1
0
mirror of https://github.com/ascribe/onion.git synced 2025-02-14 21:10:27 +01:00
Merge branch 'master' of bitbucket.org:ascribe/onion
This commit is contained in:
Cevo 2015-09-15 14:06:44 +02:00
commit dceb087f20
41 changed files with 674 additions and 438 deletions

View File

@ -189,17 +189,7 @@ function bundle(watch) {
.pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file .pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file
.on('error', notify.onError('Error: <%= error.message %>')) .on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(argv.production, uglify({ .pipe(gulpif(argv.production, uglify({
mangle: true, mangle: true
compress: {
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true
}
}))) })))
.on('error', notify.onError('Error: <%= error.message %>')) .on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulp.dest('./build/js')) .pipe(gulp.dest('./build/js'))

View File

@ -45,7 +45,6 @@ requests.defaults({
class AppGateway { class AppGateway {
start() { start() {
let settings; let settings;
let subdomain = window.location.host.split('.')[0]; let subdomain = window.location.host.split('.')[0];

View File

@ -1,27 +0,0 @@
'use strict';
import React from 'react';
let ButtonSubmitOrClose = React.createClass({
propTypes: {
submitted: React.PropTypes.bool.isRequired,
text: React.PropTypes.string.isRequired
},
render() {
if (this.props.submitted){
return (
<div className="modal-footer">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</div>
);
}
return (
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">{this.props.text}</button>
</div>
);
}
});
export default ButtonSubmitOrClose;

View File

@ -1,32 +0,0 @@
'use strict';
import React from 'react';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js'
let ButtonSubmitOrClose = React.createClass({
propTypes: {
submitted: React.PropTypes.bool.isRequired,
text: React.PropTypes.string.isRequired,
onClose: React.PropTypes.func.isRequired
},
render() {
if (this.props.submitted){
return (
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>
);
}
return (
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">{this.props.text}</button>
<button className="btn btn-ascribe-inv" onClick={this.props.onClose}>{getLangText('CLOSE')}</button>
</div>
);
}
});
export default ButtonSubmitOrClose;

View File

@ -50,7 +50,7 @@ let EditionContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.edition) { if(this.state.edition && this.state.edition.title) {
return ( return (
<Edition <Edition
edition={this.state.edition} edition={this.state.edition}

View File

@ -39,7 +39,7 @@ let FurtherDetailsFileuploader = React.createClass({
return null; return null;
} }
let otherDataIds = this.props.otherData ? this.props.otherData.map((data)=>{return data.id; }).join() : null; let otherDataIds = this.props.otherData ? this.props.otherData.map((data) => data.id).join() : null;
return ( return (
<Property <Property

View File

@ -206,7 +206,7 @@ let PieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -41,7 +41,9 @@ let Form = React.createClass({
// It will make use of the GlobalNotification // It will make use of the GlobalNotification
isInline: React.PropTypes.bool, isInline: React.PropTypes.bool,
autoComplete: React.PropTypes.string autoComplete: React.PropTypes.string,
onReset: React.PropTypes.func
}, },
getDefaultProps() { getDefaultProps() {
@ -60,9 +62,15 @@ let Form = React.createClass({
}; };
}, },
reset(){ reset() {
for (let ref in this.refs){ // If onReset prop is defined from outside,
if (typeof this.refs[ref].reset === 'function'){ // notify component that a form reset is happening.
if(typeof this.props.onReset === 'function') {
this.props.onReset();
}
for(let ref in this.refs) {
if(typeof this.refs[ref].reset === 'function') {
this.refs[ref].reset(); this.refs[ref].reset();
} }
} }
@ -70,7 +78,6 @@ let Form = React.createClass({
}, },
submit(event){ submit(event){
if(event) { if(event) {
event.preventDefault(); event.preventDefault();
} }
@ -79,7 +86,7 @@ let Form = React.createClass({
this.clearErrors(); this.clearErrors();
// selecting http method based on props // selecting http method based on props
if(this[this.props.method]) { if(this[this.props.method] && typeof this[this.props.method] === 'function') {
window.setTimeout(() => this[this.props.method](), 100); window.setTimeout(() => this[this.props.method](), 100);
} else { } else {
throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')'); throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')');
@ -100,13 +107,14 @@ let Form = React.createClass({
.catch(this.handleError); .catch(this.handleError);
}, },
getFormData(){ getFormData() {
let data = {}; let data = {};
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(typeof this.props.getFormData === 'function') {
data = mergeOptionsWithDuplicates(data, this.props.getFormData()); data = mergeOptionsWithDuplicates(data, this.props.getFormData());
} }
@ -114,15 +122,16 @@ let Form = React.createClass({
}, },
handleChangeChild(){ handleChangeChild(){
this.setState({edited: true}); this.setState({ edited: true });
}, },
handleSuccess(response){ handleSuccess(response){
if ('handleSuccess' in this.props){ if(typeof this.props.handleSuccess === 'function') {
this.props.handleSuccess(response); this.props.handleSuccess(response);
} }
for (var ref in this.refs){
if ('handleSuccess' in this.refs[ref]){ for(let ref in this.refs) {
if(this.refs[ref] && typeof this.refs[ref].handleSuccess === 'function'){
this.refs[ref].handleSuccess(); this.refs[ref].handleSuccess();
} }
} }
@ -134,15 +143,14 @@ let Form = React.createClass({
handleError(err){ handleError(err){
if (err.json) { if (err.json) {
for (var input in err.json.errors){ for (let input in err.json.errors){
if (this.refs && this.refs[input] && this.refs[input].state) { if (this.refs && this.refs[input] && this.refs[input].state) {
this.refs[input].setErrors( err.json.errors[input]); this.refs[input].setErrors(err.json.errors[input]);
} else { } else {
this.setState({errors: this.state.errors.concat(err.json.errors[input])}); this.setState({errors: this.state.errors.concat(err.json.errors[input])});
} }
} }
} } else {
else {
let formData = this.getFormData(); let formData = this.getFormData();
// sentry shouldn't post the user's password // sentry shouldn't post the user's password
@ -164,8 +172,8 @@ let Form = React.createClass({
}, },
clearErrors(){ clearErrors(){
for (var ref in this.refs){ for(let ref in this.refs){
if ('clearErrors' in this.refs[ref]){ if (this.refs[ref] && typeof this.refs[ref].clearErrors === 'function'){
this.refs[ref].clearErrors(); this.refs[ref].clearErrors();
} }
} }
@ -185,8 +193,16 @@ let Form = React.createClass({
buttons = ( buttons = (
<div className="row" style={{margin: 0}}> <div className="row" style={{margin: 0}}>
<p className="pull-right"> <p className="pull-right">
<Button className="btn btn-default btn-sm ascribe-margin-1px" type="submit">{this.props.buttonSubmitText}</Button> <Button
<Button className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.reset}>CANCEL</Button> className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">
{this.props.buttonSubmitText}
</Button>
<Button
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
type="reset">
CANCEL
</Button>
</p> </p>
</div> </div>
); );
@ -251,6 +267,7 @@ let Form = React.createClass({
role="form" role="form"
className={className} className={className}
onSubmit={this.submit} onSubmit={this.submit}
onReset={this.reset}
autoComplete={this.props.autoComplete}> autoComplete={this.props.autoComplete}>
{this.getFakeAutocompletableInputs()} {this.getFakeAutocompletableInputs()}
{this.getErrors()} {this.getErrors()}

View File

@ -69,10 +69,11 @@ let LoanForm = React.createClass({
}, },
handleOnChange(event) { handleOnChange(event) {
let potentialEmail = event.target.value; // event.target.value is the submitted email of the loanee
if(event && event.target && event.target.value && event.target.value.match(/.*@.*/)) {
if(potentialEmail.match(/.*@.*/)) { LoanContractActions.fetchLoanContract(event.target.value);
LoanContractActions.fetchLoanContract(potentialEmail); } else {
LoanContractActions.flushLoanContract();
} }
}, },
@ -143,6 +144,7 @@ let LoanForm = React.createClass({
ref='form' ref='form'
url={this.props.url} url={this.props.url}
getFormData={this.getFormData} getFormData={this.getFormData}
onReset={this.handleOnChange}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={this.getButtons()} buttons={this.getButtons()}
spinner={ spinner={

View File

@ -7,13 +7,10 @@ import UserActions from '../../actions/user_actions';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import InputFineUploader from './input_fineuploader';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
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'; import { mergeOptions } from '../../utils/general_utils';
import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils'; import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
@ -45,7 +42,6 @@ let RegisterPieceForm = React.createClass({
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
{ {
digitalWorkKey: null,
isUploadReady: false isUploadReady: false
}, },
UserStore.getState() UserStore.getState()
@ -65,18 +61,6 @@ let RegisterPieceForm = React.createClass({
this.setState(state); this.setState(state);
}, },
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) { setIsUploadReady(isReady) {
this.setState({ this.setState({
isUploadReady: isReady isUploadReady: isReady
@ -94,14 +78,15 @@ let RegisterPieceForm = React.createClass({
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={ApiUrls.pieces_list} url={ApiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={<button buttons={
type="submit" <button
className="btn ascribe-btn ascribe-btn-login" type="submit"
disabled={!this.state.isUploadReady || this.props.disabled}> className="btn ascribe-btn ascribe-btn-login"
{this.props.submitMessage} disabled={!this.state.isUploadReady || this.props.disabled}>
</button>} {this.props.submitMessage}
</button>
}
spinner={ spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
@ -111,9 +96,9 @@ let RegisterPieceForm = React.createClass({
<h3>{this.props.headerMessage}</h3> <h3>{this.props.headerMessage}</h3>
</div> </div>
<Property <Property
name="digital_work_key"
ignoreFocus={true}> ignoreFocus={true}>
<FileUploader <InputFineUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady} setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={isReadyForFormSubmission} isReadyForFormSubmission={isReadyForFormSubmission}
isFineUploaderActive={this.props.isFineUploaderActive} isFineUploaderActive={this.props.isFineUploaderActive}
@ -152,73 +137,4 @@ let RegisterPieceForm = React.createClass({
} }
}); });
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
},
render() {
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
// isFineUploaderActive
if(typeof this.props.disabled !== 'undefined') {
editable = !this.props.disabled;
}
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
submitKey={this.props.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} />
);
}
});
export default RegisterPieceForm; export default RegisterPieceForm;

View File

@ -18,7 +18,8 @@ let InputDate = React.createClass({
getInitialState() { getInitialState() {
return { return {
value: null value: null,
value_moment: null
}; };
}, },
@ -45,6 +46,10 @@ let InputDate = React.createClass({
}); });
}, },
reset() {
this.setState(this.getInitialState());
},
render() { render() {
return ( return (
<div> <div>

View File

@ -0,0 +1,95 @@
'use strict';
import React from 'react';
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';
let InputFileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
},
getInitialState() {
return {
value: null
};
},
submitKey(key){
this.setState({
value: key
});
},
reset() {
this.refs.fineuploader.reset();
},
render() {
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
// isFineUploaderActive
if(typeof this.props.disabled !== 'undefined') {
editable = !this.props.disabled;
}
return (
<ReactS3FineUploader
ref="fineuploader"
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
submitKey={this.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} />
);
}
});
export default InputFileUploader;

View File

@ -4,6 +4,7 @@ import React from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
let InputTextAreaToggable = React.createClass({ let InputTextAreaToggable = React.createClass({
propTypes: { propTypes: {
editable: React.PropTypes.bool.isRequired, editable: React.PropTypes.bool.isRequired,
@ -17,14 +18,17 @@ let InputTextAreaToggable = React.createClass({
value: this.props.defaultValue value: this.props.defaultValue
}; };
}, },
handleChange(event) { handleChange(event) {
this.setState({value: event.target.value}); this.setState({value: event.target.value});
this.props.onChange(event); this.props.onChange(event);
}, },
render() { render() {
let className = 'form-control ascribe-textarea'; let className = 'form-control ascribe-textarea';
let textarea = null; let textarea = null;
if (this.props.editable){
if(this.props.editable) {
className = className + ' ascribe-textarea-editable'; className = className + ' ascribe-textarea-editable';
textarea = ( textarea = (
<TextareaAutosize <TextareaAutosize
@ -37,10 +41,10 @@ let InputTextAreaToggable = React.createClass({
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
placeholder={this.props.placeholder} /> placeholder={this.props.placeholder} />
); );
} } else {
else{
textarea = <pre className="ascribe-pre">{this.state.value}</pre>; textarea = <pre className="ascribe-pre">{this.state.value}</pre>;
} }
return textarea; return textarea;
} }
}); });

View File

@ -6,8 +6,11 @@ import ReactAddons from 'react/addons';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import Tooltip from 'react-bootstrap/lib/Tooltip';
import AppConstants from '../../constants/application_constants';
import { mergeOptions } from '../../utils/general_utils'; import { mergeOptions } from '../../utils/general_utils';
let Property = React.createClass({ let Property = React.createClass({
propTypes: { propTypes: {
hidden: React.PropTypes.bool, hidden: React.PropTypes.bool,
@ -29,8 +32,11 @@ let Property = React.createClass({
handleChange: React.PropTypes.func, handleChange: React.PropTypes.func,
ignoreFocus: React.PropTypes.bool, ignoreFocus: React.PropTypes.bool,
className: React.PropTypes.string, className: React.PropTypes.string,
onClick: React.PropTypes.func, onClick: React.PropTypes.func,
onChange: React.PropTypes.func, onChange: React.PropTypes.func,
onBlur: React.PropTypes.func,
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
@ -87,21 +93,41 @@ let Property = React.createClass({
}, },
reset() { reset() {
let input = this.refs.input;
// maybe do reset by reload instead of front end state? // maybe do reset by reload instead of front end state?
this.setState({value: this.state.initialValue}); this.setState({value: this.state.initialValue});
// resets the value of a custom react component input if(input.state && input.state.value) {
this.refs.input.state.value = this.state.initialValue; // resets the value of a custom react component input
input.state.value = this.state.initialValue;
}
// resets the value of a plain HTML5 input // For some reason, if we set the value of a non HTML element (but a custom input),
this.refs.input.getDOMNode().value = this.state.initialValue; // after a reset, the value will be be propagated to this component.
//
// Therefore we have to make sure only to reset the initial value
// of HTML inputs (which we determine by checking if there 'type' attribute matches
// the ones included in AppConstants.possibleInputTypes).
let inputDOMNode = input.getDOMNode();
if(inputDOMNode.type && typeof inputDOMNode.type === 'string' &&
AppConstants.possibleInputTypes.indexOf(inputDOMNode.type.toLowerCase()) > -1) {
inputDOMNode.value = this.state.initialValue;
}
// For some inputs, reseting state.value is not enough to visually reset the
// component.
//
// So if the input actually needs a visual reset, it needs to implement
// a dedicated reset method.
if(typeof input.reset === 'function') {
input.reset();
}
}, },
handleChange(event) { handleChange(event) {
this.props.handleChange(event); this.props.handleChange(event);
if ('onChange' in this.props) { if (typeof this.props.onChange === 'function') {
this.props.onChange(event); this.props.onChange(event);
} }
@ -117,7 +143,7 @@ let Property = React.createClass({
// if onClick is defined from the outside, // if onClick is defined from the outside,
// just call it // just call it
if(this.props.onClick) { if(typeof this.props.onClick === 'function') {
this.props.onClick(); this.props.onClick();
} }
@ -132,7 +158,7 @@ let Property = React.createClass({
isFocused: false isFocused: false
}); });
if(this.props.onBlur) { if(typeof this.props.onBlur === 'function') {
this.props.onBlur(event); this.props.onBlur(event);
} }
}, },
@ -190,6 +216,7 @@ let Property = React.createClass({
}, },
render() { render() {
let footer = null;
let tooltip = <span/>; let tooltip = <span/>;
let style = this.props.style ? mergeOptions({}, this.props.style) : {}; let style = this.props.style ? mergeOptions({}, this.props.style) : {};
@ -199,7 +226,7 @@ let Property = React.createClass({
{this.props.tooltip} {this.props.tooltip}
</Tooltip>); </Tooltip>);
} }
let footer = null;
if(this.props.footer){ if(this.props.footer){
footer = ( footer = (
<div className="ascribe-property-footer"> <div className="ascribe-property-footer">
@ -223,7 +250,7 @@ let Property = React.createClass({
overlay={tooltip}> overlay={tooltip}>
<div className={'ascribe-settings-property ' + this.props.className}> <div className={'ascribe-settings-property ' + this.props.className}>
{this.state.errors} {this.state.errors}
<span>{ this.props.label}</span> <span>{this.props.label}</span>
{this.renderChildren(style)} {this.renderChildren(style)}
{footer} {footer}
</div> </div>

View File

@ -42,6 +42,13 @@ let PropertyCollapsile = React.createClass({
} }
}, },
reset() {
// If the child input is a native HTML element, it will be reset automatically
// by the DOM.
// However, we need to collapse this component again.
this.setState(this.getInitialState());
},
render() { render() {
let tooltip = <span/>; let tooltip = <span/>;
if (this.props.tooltip){ if (this.props.tooltip){

View File

@ -28,12 +28,20 @@ let Other = React.createClass({
}, },
render() { render() {
let ext = this.props.url.split('.').pop(); let filename = this.props.url.split('/').pop();
let tokens = filename.split('.');
let preview;
if (tokens.length > 1) {
preview = '.' + tokens.pop();
} else {
preview = 'file';
}
return ( return (
<Panel className="media-other"> <Panel className="media-other">
<p className="text-center"> <p className="text-center">
.{ext} {preview}
</p> </p>
</Panel> </Panel>
); );
@ -200,7 +208,8 @@ let MediaPlayer = React.createClass({
<br />You can leave this page and check back on the status later.</em> <br />You can leave this page and check back on the status later.</em>
</p> </p>
<ProgressBar now={this.props.encodingStatus} <ProgressBar now={this.props.encodingStatus}
label='%(percent)s%' /> label="%(percent)s%"
className="ascribe-progress-bar" />
</div> </div>
); );
} else { } else {

View File

@ -4,13 +4,12 @@ import React from 'react';
import Router from 'react-router'; import Router from 'react-router';
import ReactAddons from 'react/addons'; import ReactAddons from 'react/addons';
import Col from 'react-bootstrap/lib/Col';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
let State = Router.State; let State = Router.State;
let Navigation = Router.Navigation; let Navigation = Router.Navigation;
let SlidesContainer = React.createClass({ let SlidesContainer = React.createClass({
propTypes: { propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element), children: React.PropTypes.arrayOf(React.PropTypes.element),
@ -30,12 +29,15 @@ let SlidesContainer = React.createClass({
let slideNum = -1; let slideNum = -1;
let startFrom = -1; let startFrom = -1;
// We can actually need to check if slide_num is present as a key in queryParams.
// We do not really care about its value though...
if(queryParams && 'slide_num' in queryParams) { if(queryParams && 'slide_num' in queryParams) {
slideNum = parseInt(queryParams.slide_num, 10); slideNum = parseInt(queryParams.slide_num, 10);
} }
// if slide_num is not set, this will be done in componentDidMount // if slide_num is not set, this will be done in componentDidMount
// the query param 'start_from' removes all slide children before the respective number // the query param 'start_from' removes all slide children before the respective number
// Also, we use the 'in' keyword for the same reason as above in 'slide_num'
if(queryParams && 'start_from' in queryParams) { if(queryParams && 'start_from' in queryParams) {
startFrom = parseInt(queryParams.start_from, 10); startFrom = parseInt(queryParams.start_from, 10);
} }
@ -51,6 +53,9 @@ let SlidesContainer = React.createClass({
componentDidMount() { componentDidMount() {
// 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();
// We use 'in' to check if the key is present in the user's browser url bar,
// we do not really care about its value at this point
if(!('slide_num' in queryParams)) { if(!('slide_num' in queryParams)) {
// we're first requiring all the other possible queryParams and then set // we're first requiring all the other possible queryParams and then set

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import ProgressBar from 'react-progressbar'; import ProgressBar from 'react-bootstrap/lib/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';
@ -169,12 +169,16 @@ let FileDragAndDrop = React.createClass({
if(this.props.hashingProgress !== -2) { if(this.props.hashingProgress !== -2) {
return ( return (
<div className={className}> <div className={className}>
<p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p> <div className="file-drag-and-drop-hashing-dialog">
<p> <p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p>
<span>{Math.ceil(this.props.hashingProgress)}%</span> <p>
<a onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</a> <a onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</a>
</p> </p>
<ProgressBar completed={this.props.hashingProgress} color="#48DACB"/> <ProgressBar
now={Math.ceil(this.props.hashingProgress)}
label="%(percent)s%"
className="ascribe-progress-bar"/>
</div>
</div> </div>
); );
} else { } else {

View File

@ -32,7 +32,7 @@ let FileDragAndDropDialog = React.createClass({
queryParamsUpload.method = 'upload'; queryParamsUpload.method = 'upload';
return ( return (
<span className="file-drag-and-drop-dialog present-options"> <div className="file-drag-and-drop-dialog present-options">
<p>{getLangText('Would you rather')}</p> <p>{getLangText('Would you rather')}</p>
<Link <Link
to={this.getPath()} to={this.getPath()}
@ -51,12 +51,12 @@ let FileDragAndDropDialog = React.createClass({
{getLangText('Upload and hash your work')} {getLangText('Upload and hash your work')}
</span> </span>
</Link> </Link>
</span> </div>
); );
} else { } else {
if(this.props.multipleFiles) { if(this.props.multipleFiles) {
return ( return (
<span className="file-drag-and-drop-dialog"> <div className="file-drag-and-drop-dialog">
<p>{getLangText('Drag files here')}</p> <p>{getLangText('Drag files here')}</p>
<p>{getLangText('or')}</p> <p>{getLangText('or')}</p>
<span <span
@ -64,13 +64,13 @@ let FileDragAndDropDialog = React.createClass({
onClick={this.props.onClick}> onClick={this.props.onClick}>
{getLangText('choose files to upload')} {getLangText('choose files to upload')}
</span> </span>
</span> </div>
); );
} else { } else {
let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload'); let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload');
return ( return (
<span className="file-drag-and-drop-dialog"> <div className="file-drag-and-drop-dialog">
<p>{getLangText('Drag a file here')}</p> <p>{getLangText('Drag a file here')}</p>
<p>{getLangText('or')}</p> <p>{getLangText('or')}</p>
<span <span
@ -78,7 +78,7 @@ let FileDragAndDropDialog = React.createClass({
onClick={this.props.onClick}> onClick={this.props.onClick}>
{dialog} {dialog}
</span> </span>
</span> </div>
); );
} }
} }

View File

@ -72,7 +72,7 @@ let FileDragAndDropPreview = React.createClass({
if(this.props.areAssetsEditable) { if(this.props.areAssetsEditable) {
removeBtn = (<div className="delete-file"> removeBtn = (<div className="delete-file">
<span <span
className="glyphicon glyphicon-remove text-center" className="glyphicon glyphicon-remove text-center"
aria-hidden="true" aria-hidden="true"
title={getLangText('Remove file')} title={getLangText('Remove file')}

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import ProgressBar from 'react-progressbar'; import ProgressBar from 'react-bootstrap/lib/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';
@ -60,7 +60,9 @@ let FileDragAndDropPreviewImage = React.createClass({
<div <div
className="file-drag-and-drop-preview-image" className="file-drag-and-drop-preview-image"
style={imageStyle}> style={imageStyle}>
<ProgressBar completed={this.props.progress} color="black"/> <ProgressBar
now={Math.ceil(this.props.progress)}
className="ascribe-progress-bar ascribe-progress-bar-xs"/>
{actionSymbol} {actionSymbol}
</div> </div>
); );

View File

@ -3,6 +3,10 @@
import React from 'react'; import React from 'react';
import FileDragAndDropPreview from './file_drag_and_drop_preview'; import FileDragAndDropPreview from './file_drag_and_drop_preview';
import FileDragAndDropPreviewProgress from './file_drag_and_drop_preview_progress';
import { displayValidFilesFilter } from './react_s3_fine_uploader_utils';
let FileDragAndDropPreviewIterator = React.createClass({ let FileDragAndDropPreviewIterator = React.createClass({
propTypes: { propTypes: {
@ -16,26 +20,37 @@ let FileDragAndDropPreviewIterator = React.createClass({
}, },
render() { render() {
if(this.props.files) { let {
files,
handleDeleteFile,
handleCancelFile,
handlePauseFile,
handleResumeFile,
areAssetsDownloadable,
areAssetsEditable
} = this.props;
files = files.filter(displayValidFilesFilter);
if(files && files.length > 0) {
return ( return (
<div> <div className="file-drag-and-drop-preview-iterator">
{this.props.files.map((file, i) => { <div className="file-drag-and-drop-preview-iterator-spacing">
if(file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1) { {files.map((file, i) => {
return ( return (
<FileDragAndDropPreview <FileDragAndDropPreview
key={i} key={i}
file={file} file={file}
handleDeleteFile={this.props.handleDeleteFile} handleDeleteFile={handleDeleteFile}
handleCancelFile={this.props.handleCancelFile} handleCancelFile={handleCancelFile}
handlePauseFile={this.props.handlePauseFile} handlePauseFile={handlePauseFile}
handleResumeFile={this.props.handleResumeFile} handleResumeFile={handleResumeFile}
areAssetsDownloadable={this.props.areAssetsDownloadable} areAssetsDownloadable={areAssetsDownloadable}
areAssetsEditable={this.props.areAssetsEditable}/> areAssetsEditable={areAssetsEditable}/>
); );
} else { })}
return null; </div>
} <FileDragAndDropPreviewProgress files={files} />
})}
</div> </div>
); );
} else { } else {

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import ProgressBar from 'react-progressbar'; import ProgressBar from 'react-bootstrap/lib/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';
@ -55,7 +55,9 @@ let FileDragAndDropPreviewOther = React.createClass({
return ( return (
<div <div
className="file-drag-and-drop-preview"> className="file-drag-and-drop-preview">
<ProgressBar completed={this.props.progress} color="black"/> <ProgressBar
now={Math.ceil(this.props.progress)}
className="ascribe-progress-bar ascribe-progress-bar-xs"/>
<div className="file-drag-and-drop-preview-table-wrapper"> <div className="file-drag-and-drop-preview-table-wrapper">
<div className="file-drag-and-drop-preview-other"> <div className="file-drag-and-drop-preview-other">
{actionSymbol} {actionSymbol}

View File

@ -0,0 +1,64 @@
'use strict';
import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import { displayValidProgressFilesFilter } from './react_s3_fine_uploader_utils';
let FileDragAndDropPreviewProgress = React.createClass({
propTypes: {
files: React.PropTypes.array
},
calcOverallFileSize() {
let overallFileSize = 0;
let files = this.props.files.filter(displayValidProgressFilesFilter);
// We just sum up all files' sizes
for(let i = 0; i < files.length; i++) {
overallFileSize += files[i].size;
}
return overallFileSize;
},
calcOverallProgress() {
let overallProgress = 0;
let overallFileSize = this.calcOverallFileSize();
let files = this.props.files.filter(displayValidProgressFilesFilter);
// We calculate the overall progress by summing the individuals
// files' progresses in relation to their size
for(let i = 0; i < files.length; i++) {
overallProgress += files[i].size / overallFileSize * files[i].progress;
}
return overallProgress;
},
render() {
let overallProgress = this.calcOverallProgress();
let overallFileSize = this.calcOverallFileSize();
let style = {
visibility: 'hidden'
};
// only visible if overallProgress is over zero
// or the overallFileSize is greater than 10MB
if(overallProgress !== 0 && overallFileSize > 10000000) {
style.visibility = 'visible';
}
return (
<ProgressBar
now={Math.ceil(overallProgress)}
label="Overall progress: %(percent)s%"
className="ascribe-progress-bar"
style={style} />
);
}
});
export default FileDragAndDropPreviewProgress;

View File

@ -17,10 +17,9 @@ 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'; import { computeHashOfFile, displayValidFilesFilter } from '../../utils/file_utils';
var ReactS3FineUploader = React.createClass({ var ReactS3FineUploader = React.createClass({
propTypes: { propTypes: {
keyRoutine: React.PropTypes.shape({ keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string, url: React.PropTypes.string,
@ -125,6 +124,7 @@ var ReactS3FineUploader = React.createClass({
bucket: 'ascribe0' bucket: 'ascribe0'
}, },
request: { request: {
//endpoint: 'https://www.ascribe.io.global.prod.fastly.net',
endpoint: 'https://ascribe0.s3.amazonaws.com', endpoint: 'https://ascribe0.s3.amazonaws.com',
accessKey: 'AKIAIVCZJ33WSCBQ3QDA' accessKey: 'AKIAIVCZJ33WSCBQ3QDA'
}, },
@ -235,6 +235,21 @@ var ReactS3FineUploader = React.createClass({
}; };
}, },
// Resets the whole react fineuploader component to its initial state
reset() {
// Cancel all currently ongoing uploads
this.state.uploader.cancelAll();
// and reset component in general
this.state.uploader.reset();
// proclaim that upload is not ready
this.props.setIsUploadReady(false);
// reset internal data structures of component
this.setState(this.getInitialState());
},
requestKey(fileId) { requestKey(fileId) {
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);
@ -325,11 +340,9 @@ var ReactS3FineUploader = React.createClass({
completed: false completed: false
}; };
let newState = React.addons.update(this.state, { let startedChunks = React.addons.update(this.state.startedChunks, { $set: chunks });
startedChunks: { $set: chunks }
});
this.setState(newState); this.setState({ startedChunks });
}, },
onUploadChunkSuccess(id, chunkData, responseJson, xhr) { onUploadChunkSuccess(id, chunkData, responseJson, xhr) {
@ -342,75 +355,65 @@ var ReactS3FineUploader = React.createClass({
chunks[chunkKey].responseJson = responseJson; chunks[chunkKey].responseJson = responseJson;
chunks[chunkKey].xhr = xhr; chunks[chunkKey].xhr = xhr;
let newState = React.addons.update(this.state, { let startedChunks = React.addons.update(this.state.startedChunks, { $set: chunks });
startedChunks: { $set: chunks }
});
this.setState(newState); this.setState({ startedChunks });
} }
}, },
onComplete(id, name, res, xhr) { onComplete(id, name, res, xhr) {
// there has been an issue with the server's connection // there has been an issue with the server's connection
if(xhr.status === 0) { if((xhr && xhr.status === 0) || res.error) {
console.logGlobal(new Error(res.error || 'Complete was called but there wasn\t a success'), false, {
console.logGlobal(new Error('Complete was called but there wasn\t a success'), false, {
files: this.state.filesToUpload, files: this.state.filesToUpload,
chunks: this.state.chunks chunks: this.state.chunks
}); });
} else {
let files = this.state.filesToUpload;
return; // 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].key = this.state.uploader.getKey(id);
let files = this.state.filesToUpload; let filesToUpload = React.addons.update(this.state.filesToUpload, { $set: files });
this.setState({ filesToUpload });
// Set the state of the completed file to 'upload successful' in order to // Only after the blob has been created server-side, we can make the form submittable.
// remove it from the GUI this.createBlob(files[id])
files[id].status = 'upload successful'; .then(() => {
files[id].key = this.state.uploader.getKey(id); // since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
// are optional, we'll only trigger them when they're actually defined
let newState = React.addons.update(this.state, { if(this.props.submitKey) {
filesToUpload: { $set: files } this.props.submitKey(files[id].key);
});
this.setState(newState);
// Only after the blob has been created server-side, we can make the form submittable.
this.createBlob(files[id])
.then(() => {
// 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 { } else {
this.props.setIsUploadReady(false); console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
} }
} else {
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady 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,
.catch((err) => { // the form is ready for submission or not
console.logGlobal(err, false, { if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
files: this.state.filesToUpload, // if so, set uploadstatus to true
chunks: this.state.chunks 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, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
}); });
let notification = new GlobalNotificationModel(err.message, 'danger', 5000); }
GlobalNotificationActions.appendGlobalNotification(notification);
});
}, },
onError(id, name, errorReason) { onError(id, name, errorReason) {
@ -439,7 +442,6 @@ var ReactS3FineUploader = React.createClass({
}, },
onCancel(id) { onCancel(id) {
// when a upload is canceled, we need to update this components file array // when a upload is canceled, we need to update this components file array
this.setStatusOfFile(id, 'canceled'); this.setStatusOfFile(id, 'canceled');
@ -458,16 +460,17 @@ var ReactS3FineUploader = React.createClass({
} else { } else {
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader'); console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
} }
return true;
}, },
onProgress(id, name, uploadedBytes, totalBytes) { onProgress(id, name, uploadedBytes, totalBytes) {
let filesToUpload = React.addons.update(this.state.filesToUpload, {
let newState = React.addons.update(this.state, { [id]: {
filesToUpload: { [id]: { progress: { $set: (uploadedBytes / totalBytes) * 100}
progress: { $set: (uploadedBytes / totalBytes) * 100} }
} }
}); });
this.setState(newState); this.setState({ filesToUpload });
}, },
onSessionRequestComplete(response, success) { onSessionRequestComplete(response, success) {
@ -489,8 +492,9 @@ var ReactS3FineUploader = React.createClass({
return file; return file;
}); });
let newState = React.addons.update(this.state, {filesToUpload: {$set: updatedFilesToUpload}}); let filesToUpload = React.addons.update(this.state.filesToUpload, {$set: updatedFilesToUpload});
this.setState(newState);
this.setState({filesToUpload });
} else { } else {
// server has to respond with 204 // server has to respond with 204
//let notification = new GlobalNotificationModel('Could not load attached files (Further data)', 'danger', 10000); //let notification = new GlobalNotificationModel('Could not load attached files (Further data)', 'danger', 10000);
@ -502,13 +506,11 @@ var ReactS3FineUploader = React.createClass({
onDeleteComplete(id, xhr, isError) { onDeleteComplete(id, xhr, isError) {
if(isError) { if(isError) {
let notification = new GlobalNotificationModel(getLangText('Couldn\'t delete file'), 'danger', 10000); this.setStatusOfFile(id, 'online');
let notification = new GlobalNotificationModel(getLangText('There was an error deleting your file.'), 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
} else { } else {
// To hide the file in this component, we need to set it's status to "deleted"
this.setStatusOfFile(id, 'deleted');
let notification = new GlobalNotificationModel(getLangText('File deleted'), 'success', 5000); let notification = new GlobalNotificationModel(getLangText('File deleted'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
} }
@ -530,6 +532,13 @@ var ReactS3FineUploader = React.createClass({
}, },
handleDeleteFile(fileId) { handleDeleteFile(fileId) {
// We set the files state to 'deleted' immediately, so that the user is not confused with
// the unresponsiveness of the UI
//
// If there is an error during the deletion, we will just change the status back to 'online'
// and display an error message
this.setStatusOfFile(fileId, 'deleted');
// In some instances (when the file was already uploaded and is just displayed to the user // In some instances (when the file was already uploaded and is just displayed to the user
// - for example in the loan contract or additional files dialog) // - for example in the loan contract or additional files dialog)
// fineuploader does not register an id on the file (we do, don't be confused by this!). // fineuploader does not register an id on the file (we do, don't be confused by this!).
@ -547,8 +556,6 @@ var ReactS3FineUploader = React.createClass({
// promise // promise
} else { } else {
let fileToDelete = this.state.filesToUpload[fileId]; let fileToDelete = this.state.filesToUpload[fileId];
fileToDelete.status = 'deleted';
S3Fetcher S3Fetcher
.deleteFile(fileToDelete.s3Key, fileToDelete.s3Bucket) .deleteFile(fileToDelete.s3Key, fileToDelete.s3Bucket)
.then(() => this.onDeleteComplete(fileToDelete.id, null, false)) .then(() => this.onDeleteComplete(fileToDelete.id, null, false))
@ -580,7 +587,7 @@ var ReactS3FineUploader = React.createClass({
handleUploadFile(files) { handleUploadFile(files) {
// If multiple set and user already uploaded its work, // If multiple set and user already uploaded its work,
// cancel upload // cancel upload
if(!this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0) { if(!this.props.multiple && this.state.filesToUpload.filter(displayValidFilesFilter).length > 0) {
return; return;
} }
@ -595,7 +602,7 @@ var ReactS3FineUploader = React.createClass({
files = validFiles; files = validFiles;
// Call this method to signal the outside component that an upload is in progress // Call this method to signal the outside component that an upload is in progress
if(this.props.uploadStarted && typeof this.props.uploadStarted === 'function' && files.length > 0) { if(typeof this.props.uploadStarted === 'function' && files.length > 0) {
this.props.uploadStarted(); this.props.uploadStarted();
} }
@ -736,6 +743,7 @@ var ReactS3FineUploader = React.createClass({
synchronizeFileLists(files) { 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++) {
@ -750,6 +758,22 @@ var ReactS3FineUploader = React.createClass({
// and re-add fineuploader specific information for old files as well // and re-add fineuploader specific information for old files as well
for(let i = 0; i < oldAndNewFiles.length; i++) { for(let i = 0; i < oldAndNewFiles.length; i++) {
for(let j = 0; j < oldFiles.length; j++) { for(let j = 0; j < oldFiles.length; j++) {
// EXCEPTION:
//
// Files do not necessarily come from the user's hard drive but can also be fetched
// from Amazon S3. This is handled in onSessionRequestComplete.
//
// If the user deletes one of those files, then fineuploader will still keep it in his
// files array but with key, progress undefined and size === -1 but
// status === 'upload successful'.
// This poses a problem as we depend on the amount of files that have
// status === 'upload successful', therefore once the file is synced,
// we need to tag its status as 'deleted' (which basically happens here)
if(oldAndNewFiles[i].size === -1 && (!oldAndNewFiles[i].progress || oldAndNewFiles[i].progress === 0)) {
oldAndNewFiles[i].status = 'deleted';
}
if(oldAndNewFiles[i].originalName === oldFiles[j].name) { if(oldAndNewFiles[i].originalName === oldFiles[j].name) {
oldAndNewFiles[i].progress = oldFiles[j].progress; oldAndNewFiles[i].progress = oldFiles[j].progress;
oldAndNewFiles[i].type = oldFiles[j].type; oldAndNewFiles[i].type = oldFiles[j].type;
@ -760,30 +784,23 @@ var ReactS3FineUploader = React.createClass({
} }
// set the new file array // set the new file array
let newState = React.addons.update(this.state, { let filesToUpload = React.addons.update(this.state.filesToUpload, { $set: oldAndNewFiles });
filesToUpload: { $set: oldAndNewFiles }
}); this.setState({ filesToUpload });
this.setState(newState);
}, },
setStatusOfFile(fileId, status) { setStatusOfFile(fileId, status) {
// also, sync files from state with the ones from fineuploader let changeSet = {};
let filesToUpload = JSON.parse(JSON.stringify(this.state.filesToUpload));
filesToUpload[fileId].status = status;
// is status is set to deleted or canceled, we also need to reset the progress
// back to zero
if(status === 'deleted' || status === 'canceled') { if(status === 'deleted' || status === 'canceled') {
filesToUpload[fileId].progress = 0; changeSet.progress = { $set: 0 };
} }
// set state changeSet.status = { $set: status };
let newState = React.addons.update(this.state, {
filesToUpload: { $set: filesToUpload }
});
this.setState(newState); let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet });
this.setState({ filesToUpload });
}, },
isDropzoneInactive() { isDropzoneInactive() {

View File

@ -1,5 +1,14 @@
'use strict'; 'use strict';
/**
* Filter function for filtering all deleted and canceled files
* @param {object} file A file from filesToUpload that has status as a prop.
* @return {boolean}
*/
export function displayValidFilesFilter(file) {
return file.status !== 'deleted' && file.status !== 'canceled';
}
/** /**
* Returns a boolean if there has been at least one file uploaded * Returns a boolean if there has been at least one file uploaded
* successfully without it being deleted or canceled. * successfully without it being deleted or canceled.
@ -7,10 +16,19 @@
* @return {Boolean} * @return {Boolean}
*/ */
export function isReadyForFormSubmission(files) { export function isReadyForFormSubmission(files) {
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled'); files = files.filter(displayValidFilesFilter);
if (files.length > 0 && files[0].status === 'upload successful') { if (files.length > 0 && files[0].status === 'upload successful') {
return true; return true;
} else { } else {
return false; return false;
} }
} }
/**
* Filter function for which files to integrate in the progress process
* @param {object} file A file from filesToUpload, that has a status as a prop.
* @return {boolean}
*/
export function displayValidProgressFilesFilter(file) {
return file.status !== 'deleted' && file.status !== 'canceled' && file.status !== 'online';
}

View File

@ -3,7 +3,7 @@
* *
* Copyright 2015, Widen Enterprises, Inc. info@fineuploader.com * Copyright 2015, Widen Enterprises, Inc. info@fineuploader.com
* *
* Version: 5.2.2 * Version: 5.3.0
* *
* Homepage: http://fineuploader.com * Homepage: http://fineuploader.com
* *
@ -894,7 +894,7 @@ var qq = function(element) {
}()); }());
/*global qq */ /*global qq */
qq.version = "5.2.2"; qq.version = "5.3.0";
/* globals qq */ /* globals qq */
qq.supportedFeatures = (function() { qq.supportedFeatures = (function() {
@ -1928,6 +1928,10 @@ qq.status = {
this._endpointStore.set(endpoint, id); this._endpointStore.set(endpoint, id);
}, },
setForm: function(elementOrId) {
this._updateFormSupportAndParams(elementOrId);
},
setItemLimit: function(newItemLimit) { setItemLimit: function(newItemLimit) {
this._currentItemLimit = newItemLimit; this._currentItemLimit = newItemLimit;
}, },
@ -1945,16 +1949,11 @@ qq.status = {
}, },
uploadStoredFiles: function() { uploadStoredFiles: function() {
var idToUpload;
if (this._storedIds.length === 0) { if (this._storedIds.length === 0) {
this._itemError("noFilesError"); this._itemError("noFilesError");
} }
else { else {
while (this._storedIds.length) { this._uploadStoredFiles();
idToUpload = this._storedIds.shift();
this._uploadFile(idToUpload);
}
} }
} }
}; };
@ -2038,10 +2037,11 @@ qq.status = {
}); });
}, },
_createStore: function(initialValue, readOnlyValues) { _createStore: function(initialValue, _readOnlyValues_) {
var store = {}, var store = {},
catchall = initialValue, catchall = initialValue,
perIdReadOnlyValues = {}, perIdReadOnlyValues = {},
readOnlyValues = _readOnlyValues_,
copy = function(orig) { copy = function(orig) {
if (qq.isObject(orig)) { if (qq.isObject(orig)) {
return qq.extend({}, orig); return qq.extend({}, orig);
@ -2095,8 +2095,20 @@ qq.status = {
addReadOnly: function(id, values) { addReadOnly: function(id, values) {
// Only applicable to Object stores // Only applicable to Object stores
if (qq.isObject(store)) { if (qq.isObject(store)) {
perIdReadOnlyValues[id] = perIdReadOnlyValues[id] || {}; // If null ID, apply readonly values to all files
qq.extend(perIdReadOnlyValues[id], values); if (id === null) {
if (qq.isFunction(values)) {
readOnlyValues = values;
}
else {
readOnlyValues = readOnlyValues || {};
qq.extend(readOnlyValues, values);
}
}
else {
perIdReadOnlyValues[id] = perIdReadOnlyValues[id] || {};
qq.extend(perIdReadOnlyValues[id], values);
}
} }
}, },
@ -2882,7 +2894,7 @@ qq.status = {
_onBeforeManualRetry: function(id) { _onBeforeManualRetry: function(id) {
var itemLimit = this._currentItemLimit, var itemLimit = this._currentItemLimit,
fileName; fileName;
console.log(this._handler.isValid(id));
if (this._preventRetries[id]) { if (this._preventRetries[id]) {
this.log("Retries are forbidden for id " + id, "warn"); this.log("Retries are forbidden for id " + id, "warn");
return false; return false;
@ -3005,13 +3017,14 @@ qq.status = {
this._onSubmit.apply(this, arguments); this._onSubmit.apply(this, arguments);
this._uploadData.setStatus(id, qq.status.SUBMITTED); this._uploadData.setStatus(id, qq.status.SUBMITTED);
this._onSubmitted.apply(this, arguments); this._onSubmitted.apply(this, arguments);
this._options.callbacks.onSubmitted.apply(this, arguments);
if (this._options.autoUpload) { if (this._options.autoUpload) {
this._options.callbacks.onSubmitted.apply(this, arguments);
this._uploadFile(id); this._uploadFile(id);
} }
else { else {
this._storeForLater(id); this._storeForLater(id);
this._options.callbacks.onSubmitted.apply(this, arguments);
} }
}, },
@ -3238,6 +3251,23 @@ qq.status = {
} }
}, },
_updateFormSupportAndParams: function(formElementOrId) {
this._options.form.element = formElementOrId;
this._formSupport = qq.FormSupport && new qq.FormSupport(
this._options.form, qq.bind(this.uploadStoredFiles, this), qq.bind(this.log, this)
);
if (this._formSupport && this._formSupport.attachedToForm) {
this._paramsStore.addReadOnly(null, this._formSupport.getFormInputsAsObject);
this._options.autoUpload = this._formSupport.newAutoUpload;
if (this._formSupport.newEndpoint) {
this.setEndpoint(this._formSupport.newEndpoint);
}
}
},
_upload: function(id, params, endpoint) { _upload: function(id, params, endpoint) {
var name = this.getName(id); var name = this.getName(id);
@ -3264,6 +3294,25 @@ qq.status = {
} }
}, },
_uploadStoredFiles: function() {
var idToUpload, stillSubmitting,
self = this;
while (this._storedIds.length) {
idToUpload = this._storedIds.shift();
this._uploadFile(idToUpload);
}
// If we are still waiting for some files to clear validation, attempt to upload these again in a bit
stillSubmitting = this.getUploads({status: qq.status.SUBMITTING}).length;
if (stillSubmitting) {
qq.log("Still waiting for " + stillSubmitting + " files to clear submit queue. Will re-parse stored IDs array shortly.");
setTimeout(function() {
self._uploadStoredFiles();
}, 1000);
}
},
/** /**
* Performs some internal validation checks on an item, defined in the `validation` option. * Performs some internal validation checks on an item, defined in the `validation` option.
* *
@ -5271,6 +5320,7 @@ qq.XhrUploadHandler = function(spec) {
*/ */
getResumableFilesData: function() { getResumableFilesData: function() {
var resumableFilesData = []; var resumableFilesData = [];
handler._iterateResumeRecords(function(key, uploadData) { handler._iterateResumeRecords(function(key, uploadData) {
handler.moveInProgressToRemaining(null, uploadData.chunking.inProgress, uploadData.chunking.remaining); handler.moveInProgressToRemaining(null, uploadData.chunking.inProgress, uploadData.chunking.remaining);
@ -5461,7 +5511,7 @@ qq.XhrUploadHandler = function(spec) {
_iterateResumeRecords: function(callback) { _iterateResumeRecords: function(callback) {
if (resumeEnabled) { if (resumeEnabled) {
qq.each(localStorage, function(key, item) { qq.each(localStorage, function(key, item) {
if (key.indexOf(qq.format("qq{}resume-", namespace)) === 0) { if (key.indexOf(qq.format("qq{}resume", namespace)) === 0) {
var uploadData = JSON.parse(item); var uploadData = JSON.parse(item);
callback(key, uploadData); callback(key, uploadData);
} }
@ -5728,7 +5778,9 @@ qq.WindowReceiveMessage = function(o) {
}, },
getItemByFileId: function(id) { getItemByFileId: function(id) {
return this._templating.getFileContainer(id); if (!this._templating.isHiddenForever(id)) {
return this._templating.getFileContainer(id);
}
}, },
reset: function() { reset: function() {
@ -6238,11 +6290,6 @@ qq.WindowReceiveMessage = function(o) {
dontDisplay = this._handler.isProxied(id) && this._options.scaling.hideScaled, dontDisplay = this._handler.isProxied(id) && this._options.scaling.hideScaled,
record; record;
// If we don't want this file to appear in the UI, skip all of this UI-related logic.
if (dontDisplay) {
return;
}
if (this._options.display.prependFiles) { if (this._options.display.prependFiles) {
if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) { if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
prependIndex = this._filesInBatchAddedToUi - 1; prependIndex = this._filesInBatchAddedToUi - 1;
@ -6274,7 +6321,7 @@ qq.WindowReceiveMessage = function(o) {
} }
} }
this._templating.addFile(id, this._options.formatFileName(name), prependData); this._templating.addFile(id, this._options.formatFileName(name), prependData, dontDisplay);
if (canned) { if (canned) {
this._thumbnailUrls[id] && this._templating.updateThumbnail(id, this._thumbnailUrls[id], true); this._thumbnailUrls[id] && this._templating.updateThumbnail(id, this._thumbnailUrls[id], true);
@ -6638,6 +6685,7 @@ qq.Templating = function(spec) {
HIDE_DROPZONE_ATTR = "qq-hide-dropzone", HIDE_DROPZONE_ATTR = "qq-hide-dropzone",
DROPZPONE_TEXT_ATTR = "qq-drop-area-text", DROPZPONE_TEXT_ATTR = "qq-drop-area-text",
IN_PROGRESS_CLASS = "qq-in-progress", IN_PROGRESS_CLASS = "qq-in-progress",
HIDDEN_FOREVER_CLASS = "qq-hidden-forever",
isCancelDisabled = false, isCancelDisabled = false,
generatedThumbnails = 0, generatedThumbnails = 0,
thumbnailQueueMonitorRunning = false, thumbnailQueueMonitorRunning = false,
@ -7273,7 +7321,7 @@ qq.Templating = function(spec) {
isCancelDisabled = true; isCancelDisabled = true;
}, },
addFile: function(id, name, prependInfo) { addFile: function(id, name, prependInfo, hideForever) {
var fileEl = qq.toElement(templateHtml.fileTemplate), var fileEl = qq.toElement(templateHtml.fileTemplate),
fileNameEl = getTemplateEl(fileEl, selectorClasses.file), fileNameEl = getTemplateEl(fileEl, selectorClasses.file),
uploaderEl = getTemplateEl(container, selectorClasses.uploader), uploaderEl = getTemplateEl(container, selectorClasses.uploader),
@ -7296,30 +7344,36 @@ qq.Templating = function(spec) {
fileList.appendChild(fileEl); fileList.appendChild(fileEl);
} }
hide(getProgress(id)); if (hideForever) {
hide(getSize(id)); fileEl.style.display = "none";
hide(getDelete(id)); qq(fileEl).addClass(HIDDEN_FOREVER_CLASS);
hide(getRetry(id));
hide(getPause(id));
hide(getContinue(id));
if (isCancelDisabled) {
this.hideCancel(id);
} }
else {
hide(getProgress(id));
hide(getSize(id));
hide(getDelete(id));
hide(getRetry(id));
hide(getPause(id));
hide(getContinue(id));
thumb = getThumbnail(id); if (isCancelDisabled) {
if (thumb && !thumb.src) { this.hideCancel(id);
cachedWaitingForThumbnailImg.then(function(waitingImg) { }
thumb.src = waitingImg.src;
if (waitingImg.style.maxHeight && waitingImg.style.maxWidth) {
qq(thumb).css({
maxHeight: waitingImg.style.maxHeight,
maxWidth: waitingImg.style.maxWidth
});
}
show(thumb); thumb = getThumbnail(id);
}); if (thumb && !thumb.src) {
cachedWaitingForThumbnailImg.then(function(waitingImg) {
thumb.src = waitingImg.src;
if (waitingImg.style.maxHeight && waitingImg.style.maxWidth) {
qq(thumb).css({
maxHeight: waitingImg.style.maxHeight,
maxWidth: waitingImg.style.maxWidth
});
}
show(thumb);
});
}
} }
}, },
@ -7413,6 +7467,10 @@ qq.Templating = function(spec) {
icon && qq(icon).addClass(options.classes.editable); icon && qq(icon).addClass(options.classes.editable);
}, },
isHiddenForever: function(id) {
return qq(getFile(id)).hasClass(HIDDEN_FOREVER_CLASS);
},
hideEditIcon: function(id) { hideEditIcon: function(id) {
var icon = getEditIcon(id); var icon = getEditIcon(id);
@ -7572,13 +7630,17 @@ qq.Templating = function(spec) {
}, },
generatePreview: function(id, optFileOrBlob) { generatePreview: function(id, optFileOrBlob) {
thumbGenerationQueue.push({id: id, optFileOrBlob: optFileOrBlob}); if (!this.isHiddenForever(id)) {
!thumbnailQueueMonitorRunning && generateNextQueuedPreview(); thumbGenerationQueue.push({id: id, optFileOrBlob: optFileOrBlob});
!thumbnailQueueMonitorRunning && generateNextQueuedPreview();
}
}, },
updateThumbnail: function(id, thumbnailUrl, showWaitingImg) { updateThumbnail: function(id, thumbnailUrl, showWaitingImg) {
thumbGenerationQueue.push({update: true, id: id, thumbnailUrl: thumbnailUrl, showWaitingImg: showWaitingImg}); if (!this.isHiddenForever(id)) {
!thumbnailQueueMonitorRunning && generateNextQueuedPreview(); thumbGenerationQueue.push({update: true, id: id, thumbnailUrl: thumbnailUrl, showWaitingImg: showWaitingImg});
!thumbnailQueueMonitorRunning && generateNextQueuedPreview();
}
}, },
hasDialog: function(type) { hasDialog: function(type) {
@ -9489,12 +9551,6 @@ qq.s3.XhrUploadHandler = function(spec, proxy) {
result.success, result.success,
function failure(reason, xhr) { function failure(reason, xhr) {
console.logGlobal(reason + 'in chunked.combine', false, {
uploadId,
etagMap,
result
});
result.failure(upload.done(id, xhr).response, xhr); result.failure(upload.done(id, xhr).response, xhr);
} }
); );
@ -12335,7 +12391,7 @@ qq.Scaler = function(spec, log) {
"use strict"; "use strict";
var self = this, var self = this,
includeReference = spec.sendOriginal, includeOriginal = spec.sendOriginal,
orient = spec.orient, orient = spec.orient,
defaultType = spec.defaultType, defaultType = spec.defaultType,
defaultQuality = spec.defaultQuality / 100, defaultQuality = spec.defaultQuality / 100,
@ -12385,16 +12441,18 @@ qq.Scaler = function(spec, log) {
}); });
}); });
includeReference && records.push({ records.push({
uuid: originalFileUuid, uuid: originalFileUuid,
name: originalFileName, name: originalFileName,
blob: originalBlob size: originalBlob.size,
blob: includeOriginal ? originalBlob : null
}); });
} }
else { else {
records.push({ records.push({
uuid: originalFileUuid, uuid: originalFileUuid,
name: originalFileName, name: originalFileName,
size: originalBlob.size,
blob: originalBlob blob: originalBlob
}); });
} }
@ -12413,19 +12471,17 @@ qq.Scaler = function(spec, log) {
proxyGroupId = qq.getUniqueId(); proxyGroupId = qq.getUniqueId();
qq.each(self.getFileRecords(uuid, name, file), function(idx, record) { qq.each(self.getFileRecords(uuid, name, file), function(idx, record) {
var relatedBlob = file, var blobSize = record.size,
relatedSize = size,
id; id;
if (record.blob instanceof qq.BlobProxy) { if (record.blob instanceof qq.BlobProxy) {
relatedBlob = record.blob; blobSize = -1;
relatedSize = -1;
} }
id = uploadData.addFile({ id = uploadData.addFile({
uuid: record.uuid, uuid: record.uuid,
name: record.name, name: record.name,
size: relatedSize, size: blobSize,
batchId: batchId, batchId: batchId,
proxyGroupId: proxyGroupId proxyGroupId: proxyGroupId
}); });
@ -12437,10 +12493,13 @@ qq.Scaler = function(spec, log) {
originalId = id; originalId = id;
} }
addFileToHandler(id, relatedBlob); if (record.blob) {
addFileToHandler(id, record.blob);
fileList.push({id: id, file: relatedBlob}); fileList.push({id: id, file: record.blob});
}
else {
uploadData.setStatus(id, qq.status.REJECTED);
}
}); });
// If we are potentially uploading an original file and some scaled versions, // If we are potentially uploading an original file and some scaled versions,
@ -12453,8 +12512,8 @@ qq.Scaler = function(spec, log) {
qqparentsize: uploadData.retrieve({id: originalId}).size qqparentsize: uploadData.retrieve({id: originalId}).size
}; };
// Make SURE the UUID for each scaled image is sent with the upload request, // Make sure the UUID for each scaled image is sent with the upload request,
// to be consistent (since we need to ensure it is sent for the original file as well). // to be consistent (since we may need to ensure it is sent for the original file as well).
params[uuidParamName] = uploadData.retrieve({id: scaledId}).uuid; params[uuidParamName] = uploadData.retrieve({id: scaledId}).uuid;
uploadData.setParentId(scaledId, originalId); uploadData.setParentId(scaledId, originalId);
@ -14411,4 +14470,4 @@ code.google.com/p/crypto-js/wiki/License
C.HmacSHA1 = Hasher._createHmacHelper(SHA1); C.HmacSHA1 = Hasher._createHmacHelper(SHA1);
}()); }());
/*! 2015-06-09 */ /*! 2015-08-26 */

File diff suppressed because one or more lines are too long

View File

@ -90,14 +90,23 @@ let PieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
/*
This really needs a refactor!
- Tim
*/
// Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) || let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ? (this.state.currentUser.is_judge && !this.state.piece.selected )) ?
<span className="glyphicon glyphicon-eye-close" aria-hidden="true"/> : this.state.piece.artist_name; <span className="glyphicon glyphicon-eye-close" aria-hidden="true"/> : this.state.piece.artist_name;
// Only show the artist email if you are a judge and the piece is shortlisted // Only show the artist email if you are a judge and the piece is shortlisted
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ? let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null; <DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -68,7 +68,7 @@ let CylandPieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -72,6 +72,9 @@ let CylandRegisterPiece = React.createClass({
// we may need to enter the process at step 1 or 2. // we may need to enter the process at step 1 or 2.
// If this is the case, we'll need the piece number to complete submission. // If this is the case, we'll need the piece number to complete submission.
// It is encoded in the URL as a queryParam and we're checking for it here. // It is encoded in the URL as a queryParam and we're checking for it here.
//
// We're using 'in' here as we want to know if 'piece_id' is present in the url,
// we don't care about the value.
if(queryParams && 'piece_id' in queryParams) { if(queryParams && 'piece_id' in queryParams) {
PieceActions.fetchOne(queryParams.piece_id); PieceActions.fetchOne(queryParams.piece_id);
} }

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece'; import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece';
@ -17,8 +16,6 @@ import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
import AclProxy from '../../../../../acl_proxy'; import AclProxy from '../../../../../acl_proxy';
import AclButton from '../../../../../ascribe_buttons/acl_button';
import { getLangText } from '../../../../../../utils/lang_utils'; import { getLangText } from '../../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../../utils/general_utils';

View File

@ -58,7 +58,7 @@ let IkonotvSubmitButton = React.createClass({
<InputCheckbox> <InputCheckbox>
<span> <span>
{' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '} {' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
(<a href="https://d1qjsxua1o9x03.cloudfront.net/live/743394beff4b1282ba735e5e3723ed74/contract/bbc92f1d-4504-49f8-818c-8dd7113c6e06.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}> (<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-tos.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')} {getLangText('read')}
</a>) </a>)
</span> </span>

View File

@ -133,7 +133,7 @@ let IkonotvPieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -53,6 +53,10 @@ let constants = {
'ga': 'UA-60614729-2' 'ga': 'UA-60614729-2'
}, },
// These are all possible types that are currently supported in HTML5 for the input element
// Source: http://www.w3schools.com/tags/att_input_type.asp
'possibleInputTypes': ['button', 'checkbox', 'color', 'date', 'datetime', 'datetime-local', 'email', 'file', 'hidden', 'image', 'month', 'number', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'text', 'time', 'url', 'week'],
// in case of whitelabel customization, we store stuff here // in case of whitelabel customization, we store stuff here
'whitelabel': {}, 'whitelabel': {},
'raven': { 'raven': {

View File

@ -3,7 +3,6 @@
import alt from '../alt'; import alt from '../alt';
import EventActions from '../actions/event_actions'; import EventActions from '../actions/event_actions';
class GoogleAnalyticsHandler { class GoogleAnalyticsHandler {
constructor() { constructor() {
this.bindActions(EventActions); this.bindActions(EventActions);

View File

@ -73,9 +73,8 @@
"q": "^1.4.1", "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.24.3", "react-bootstrap": "^0.25.1",
"react-datepicker": "^0.12.0", "react-datepicker": "^0.12.0",
"react-progressbar": "^1.1.0",
"react-router": "^0.13.3", "react-router": "^0.13.3",
"react-router-bootstrap": "~0.16.0", "react-router-bootstrap": "~0.16.0",
"react-star-rating": "~1.3.2", "react-star-rating": "~1.3.2",

View File

@ -12,8 +12,13 @@
} }
.media-other { .media-other {
font-size: 500%;
color: #cccccc; color: #cccccc;
font-size: 500%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
.audiojs { .audiojs {

View File

@ -10,7 +10,6 @@
cursor: default !important; cursor: default !important;
padding: 1.5em 0 1.5em 0;
} }
.inactive-dropzone { .inactive-dropzone {
@ -29,6 +28,27 @@
} }
} }
.file-drag-and-drop-preview-iterator {
margin: 2.5em 0 0 0;
text-align: right;
> div:first-child {
text-align: center;
}
}
.file-drag-and-drop-preview-iterator-spacing {
margin-bottom: 1em;
}
.file-drag-and-drop-dialog {
margin: 1.5em 0 1.5em 0;
}
.file-drag-and-drop-hashing-dialog {
margin: 1.5em 0 0 0;
}
.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;

0
sass/lib/buttons.scss Normal file
View File

View File

@ -212,31 +212,22 @@ hr {
border: 1px solid $ascribe-brand-danger; border: 1px solid $ascribe-brand-danger;
} }
} }
.btn-ascribe, .btn-ascribe-inv { .btn-ascribe {
border: 1px solid #444; border: 1px solid #444;
line-height: 2em; line-height: 2em;
margin-right: 1px; margin-right: 1px;
margin-left: 0 !important; margin-left: 0 !important;
font-family: sans-serif !important; font-family: sans-serif !important;
border-radius: 0 !important; border-radius: 0 !important;
}
.btn-ascribe, .btn-ascribe-inv:active, .btn-ascribe-inv:hover {
color: #222 !important; color: #222 !important;
background-color: #FFF; background-color: #FFF;
} }
.btn-ascribe:active, .btn-ascribe:hover, .btn-ascribe-inv { .btn-ascribe:active, .btn-ascribe:hover {
color: #FFF !important; color: #FFF !important;
background-color: #444; background-color: #444;
} }
.btn-ascribe-inv:disabled, .btn-ascribe-inv:focus {
color: #444 !important;
background-color: #BBB !important;
border: 1px solid #444 !important;
}
.btn-ascribe-sm { .btn-ascribe-sm {
font-size: 12px; font-size: 12px;
line-height: 1.3em; line-height: 1.3em;
@ -450,3 +441,14 @@ hr {
padding-bottom: 30%; padding-bottom: 30%;
text-align: center; text-align: center;
} }
.ascribe-progress-bar {
margin-bottom: 0;
> .progress-bar {
background-color: $ascribe-color-green;
}
}
.ascribe-progress-bar-xs {
height: 12px;
}