1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 01:25:17 +01:00

Merge branch 'AD-416-account-settings-page-navbar' of bitbucket.org:ascribe/onion into AD-416-account-settings-page-navbar

Conflicts:
	sass/main.scss
This commit is contained in:
Tim Daubenschütz 2015-06-24 15:50:45 +02:00
commit ea88e87355
36 changed files with 15684 additions and 390 deletions

View File

@ -4,4 +4,5 @@ node_modules
js/**/__tests__ js/**/__tests__
server.js server.js
js/components/ascribe_uploader/vendor

1
.gitignore vendored
View File

@ -12,7 +12,6 @@ logs
results results
node_modules/* node_modules/*
!node_modules/react-s3-fineuploader
build build

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "node_modules/react-s3-fineuploader"]
path = node_modules/react-s3-fineuploader
url = https://bitbucket.org/ascribe/react-s3-fineuploader.git

View File

@ -69,7 +69,7 @@ gulp.task('js:build', function() {
bundle(false); bundle(false);
}); });
gulp.task('serve', ['browser-sync', 'run-server', 'sass:watch', 'copy'], function() { gulp.task('serve', ['browser-sync', 'run-server', 'lint:watch', 'sass:build', 'sass:watch', 'copy'], function() {
bundle(true); bundle(true);
}); });

View File

@ -1,4 +1,5 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -0,0 +1,34 @@
'use strict';
import alt from '../alt';
import ApplicationFetcher from '../fetchers/application_fetcher';
class ApplicationActions {
constructor() {
this.generateActions(
'updateApplications'
);
}
fetchApplication() {
ApplicationFetcher.fetch()
.then((res) => {
this.actions.updateApplications(res.applications);
})
.catch((err) => {
console.log(err);
});
}
refreshApplicationToken(applicationName) {
ApplicationFetcher.refreshToken(applicationName)
.then((res) => {
this.actions.updateApplications(res.applications);
})
.catch((err) => {
console.log(err);
});
}
}
export default alt.createActions(ApplicationActions);

View File

@ -18,6 +18,12 @@ const CollapsibleParagraph = React.createClass({
iconName: React.PropTypes.string iconName: React.PropTypes.string
}, },
getDefaultProps() {
return {
show: true
};
},
mixins: [CollapsibleMixin], mixins: [CollapsibleMixin],
getCollapsibleDOMNode(){ getCollapsibleDOMNode(){
@ -35,19 +41,23 @@ const CollapsibleParagraph = React.createClass({
render() { render() {
let styles = this.getCollapsibleClassSet(); let styles = this.getCollapsibleClassSet();
let text = this.isExpanded() ? '-' : '+'; let text = this.isExpanded() ? '[hide]' : '[show]';
return ( if(this.props.show) {
<div className="ascribe-detail-header"> return (
<div className="ascribe-edition-collapsible-wrapper"> <div className="ascribe-detail-header">
<div onClick={this.handleToggle}> <div className="ascribe-edition-collapsible-wrapper">
<span>{text} {this.props.title} </span> <div onClick={this.handleToggle}>
</div> <span>{this.props.title}</span><span className="pull-right">{text}</span>
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}> </div>
{this.props.children} <div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}>
{this.props.children}
</div>
</div> </div>
</div> </div>
</div> );
); } else {
return null;
}
} }
}); });

View File

@ -10,6 +10,9 @@ import AlertDismissable from './alert';
let Form = React.createClass({ let Form = React.createClass({
propTypes: { propTypes: {
url: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
getFormData: React.PropTypes.func,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.object, React.PropTypes.object,
React.PropTypes.array React.PropTypes.array
@ -47,6 +50,9 @@ let Form = React.createClass({
}, },
getFormData(){ getFormData(){
if ('getFormData' in this.props){
return this.props.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;
@ -66,7 +72,7 @@ let Form = React.createClass({
this.refs[ref].handleSuccess(); this.refs[ref].handleSuccess();
} }
} }
this.setState({edited: false}); this.setState({edited: false, submitted: false});
}, },
handleError(err){ handleError(err){
if (err.json) { if (err.json) {
@ -102,9 +108,11 @@ let Form = React.createClass({
if (this.state.edited){ if (this.state.edited){
buttons = ( buttons = (
<div className="pull-right"> <div className="row" style={{margin: 0}}>
<Button className="ascribe-btn" type="submit">Save</Button> <p className="pull-right">
<Button className="ascribe-btn" onClick={this.reset}>Cancel</Button> <Button className="ascribe-btn" type="submit">Save</Button>
<Button className="ascribe-btn" onClick={this.reset}>Cancel</Button>
</p>
</div> </div>
); );

View File

@ -5,47 +5,58 @@ import React from 'react';
import requests from '../../utils/requests'; import requests from '../../utils/requests';
import apiUrls from '../../constants/api_urls'; import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import Form from './form';
import Property from './property';
import InputTextAreaToggable from './input_textarea_toggable'; import InputTextAreaToggable from './input_textarea_toggable';
let PieceExtraDataForm = React.createClass({ let PieceExtraDataForm = React.createClass({
mixins: [FormMixin], propTypes: {
edition: React.PropTypes.object,
url() { handleSuccess: React.PropTypes.func,
return requests.prepareUrl(apiUrls.piece_extradata, {piece_id: this.props.editions[0].bitcoin_id}); name: React.PropTypes.string,
title: React.PropTypes.string,
editable: React.PropTypes.bool
}, },
getFormData(){
getFormData() {
let extradata = {}; let extradata = {};
extradata[this.props.name] = this.refs[this.props.name].state.value; extradata[this.props.name] = this.refs.form.refs[this.props.name].state.value;
return { return {
bitcoin_id: this.getBitcoinIds().join(), bitcoin_id: this.props.edition.bitcoin_id,
extradata: extradata extradata: extradata
}; };
}, },
render() {
renderForm() { let defaultValue = this.props.edition.extra_data[this.props.name] || '';
let defaultValue = this.props.editions[0].extra_data[this.props.name] || '';
if (defaultValue.length === 0 && !this.props.editable){ if (defaultValue.length === 0 && !this.props.editable){
return null; return null;
} }
let url = requests.prepareUrl(apiUrls.piece_extradata, {piece_id: this.props.edition.bitcoin_id});
return ( return (
<form role="form" key={this.props.name}> <Form
<h5>{this.props.title}</h5> ref='form'
<InputTextAreaToggable url={url}
ref={this.props.name} handleSuccess={this.props.handleSuccess}
className="form-control" getFormData={this.getFormData}>
defaultValue={defaultValue} <Property
rows={3} name={this.props.name}
editable={this.props.editable} label={this.props.title}
required="" editable={this.props.editable}>
onSubmit={this.submit} <InputTextAreaToggable
/> rows={3}
</form> editable={this.props.editable}
defaultValue={defaultValue}
placeholder={'Fill in ' + this.props.title}
required/>
</Property>
<Property hidden={true} name='bitcoin_id'>
<input defaultValue={this.props.edition.bitcoin_id}/>
</Property>
<hr />
</Form>
); );
} }
}); });
export default PieceExtraDataForm; export default PieceExtraDataForm;

View File

@ -12,7 +12,7 @@ let ShareForm = React.createClass({
mixins: [FormMixin], mixins: [FormMixin],
url() { url() {
return ApiUrls.ownership_shares_mail; return ApiUrls.ownership_shares;
}, },
getFormData() { getFormData() {

View File

@ -2,56 +2,29 @@
import React from 'react'; import React from 'react';
import AlertMixin from '../../mixins/alert_mixin';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import Button from 'react-bootstrap/lib/Button';
let InputTextAreaToggable = React.createClass({ let InputTextAreaToggable = React.createClass({
propTypes: { propTypes: {
editable: React.PropTypes.bool.isRequired, editable: React.PropTypes.bool.isRequired,
submitted: React.PropTypes.bool,
rows: React.PropTypes.number.isRequired, rows: React.PropTypes.number.isRequired,
onSubmit: React.PropTypes.func.isRequired,
required: React.PropTypes.string, required: React.PropTypes.string,
defaultValue: React.PropTypes.string defaultValue: React.PropTypes.string
}, },
mixins: [AlertMixin],
getInitialState() { getInitialState() {
return { return {
value: this.props.defaultValue, value: this.props.defaultValue
edited: false,
alerts: null // needed in AlertMixin
}; };
}, },
handleChange(event) { handleChange(event) {
this.setState({ this.setState({value: event.target.value});
value: event.target.value, this.props.onChange(event);
edited: true
});
},
reset(){
this.setState(this.getInitialState());
},
submit(){
this.props.onSubmit();
this.setState({edited: false});
}, },
render() { render() {
let className = 'form-control ascribe-textarea'; let className = 'form-control ascribe-textarea';
let buttons = null;
let textarea = null; let textarea = null;
if (this.props.editable && this.state.edited){
buttons = (
<div className="pull-right">
<Button className="ascribe-btn" onClick={this.submit}>Save</Button>
<Button className="ascribe-btn" onClick={this.reset}>Cancel</Button>
</div>
);
}
if (this.props.editable){ if (this.props.editable){
className = className + ' ascribe-textarea-editable'; className = className + ' ascribe-textarea-editable';
textarea = ( textarea = (
@ -61,21 +34,16 @@ let InputTextAreaToggable = React.createClass({
rows={this.props.rows} rows={this.props.rows}
required={this.props.required} required={this.props.required}
onChange={this.handleChange} onChange={this.handleChange}
placeholder='Write something...' /> onBlur={this.props.onBlur}
placeholder={this.props.placeholder} />
); );
} }
else{ else{
textarea = <pre className="ascribe-pre">{this.state.value}</pre>; textarea = <pre className="ascribe-pre">{this.state.value}</pre>;
} }
let alerts = (this.props.submitted) ? null : this.state.alerts; return textarea;
return (
<div className="form-group">
{alerts}
{textarea}
{buttons}
</div>
);
} }
}); });
export default InputTextAreaToggable; export default InputTextAreaToggable;

View File

@ -8,7 +8,9 @@ import Tooltip from 'react-bootstrap/lib/Tooltip';
let Property = React.createClass({ let Property = React.createClass({
propTypes: { propTypes: {
hidden: React.PropTypes.bool,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
tooltip: React.PropTypes.element,
label: React.PropTypes.string, label: React.PropTypes.string,
value: React.PropTypes.oneOfType([ value: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
@ -19,7 +21,8 @@ let Property = React.createClass({
getDefaultProps() { getDefaultProps() {
return { return {
editable: true editable: true,
hidden: false
}; };
}, },
@ -38,8 +41,16 @@ let Property = React.createClass({
}); });
}, },
reset(){ reset(){
// maybe do reset by reload instead of frontend state?
this.setState({value: this.state.initialValue}); this.setState({value: this.state.initialValue});
this.refs.input.getDOMNode().value = this.state.initialValue; if (this.refs.input.state){
// This is probably not the right way but easy fix
this.refs.input.state.value = this.state.initialValue;
}
else{
this.refs.input.getDOMNode().value = this.state.initialValue;
}
}, },
handleChange(event) { handleChange(event) {
@ -74,6 +85,9 @@ let Property = React.createClass({
this.setState({errors: null}); this.setState({errors: null});
}, },
getClassName() { getClassName() {
if(this.props.hidden){
return 'is-hidden';
}
if(!this.props.editable){ if(!this.props.editable){
return 'is-fixed'; return 'is-fixed';
} }

View File

@ -0,0 +1,122 @@
import React from 'react';
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
var FileDragAndDrop = React.createClass({
propTypes: {
onDragStart: React.PropTypes.func,
onDrop: React.PropTypes.func.isRequired,
onDragEnter: React.PropTypes.func,
onLeave: React.PropTypes.func,
onDragOver: React.PropTypes.func,
onDragEnd: React.PropTypes.func,
filesToUpload: React.PropTypes.array,
handleDeleteFile: React.PropTypes.func
},
handleDragStart(event) {
if (typeof this.props.onDragStart === 'function') {
this.props.onDragStart(event);
}
},
handleDrag(event) {
if (typeof this.props.onDrag === 'function') {
this.props.onDrag(event);
}
},
handleDragEnd(event) {
if (typeof this.props.onDragEnd === 'function') {
this.props.onDragEnd(event);
}
},
handleDragEnter(event) {
if (typeof this.props.onDragEnter === 'function') {
this.props.onDragEnter(event);
}
},
handleDragLeave(event) {
if (typeof this.props.onDragLeave === 'function') {
this.props.onDragLeave(event);
}
},
handleDragOver(event) {
event.preventDefault();
if (typeof this.props.onDragOver === 'function') {
this.props.onDragOver(event);
}
},
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
let files;
// handle Drag and Drop
if(event.dataTransfer && event.dataTransfer.files.length > 0) {
files = event.dataTransfer.files;
} else if(event.target.files) { // handle input type file
files = event.target.files;
}
if(typeof this.props.onDrop === 'function' && files) {
this.props.onDrop(files);
}
},
handleDeleteFile(fileId) {
// input's value is not change the second time someone
// inputs the same file again, therefore we need to reset its value
this.refs.fileinput.getDOMNode().value = '';
this.props.handleDeleteFile(fileId);
},
handleOnClick() {
// Simulate click on hidden file input
var event = document.createEvent('HTMLEvents');
event.initEvent('click', false, true);
this.refs.fileinput.getDOMNode().dispatchEvent(event);
},
render: function () {
let hasFiles = this.props.filesToUpload.length > 0;
return (
<div
className={hasFiles ? 'file-drag-and-drop has-files' : 'file-drag-and-drop' }
onClick={this.handleOnClick}
onDragStart={this.handleDragStart}
onDrag={this.handleDrop}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
onDragEnd={this.handleDragEnd}>
{hasFiles ? null : <span>Click or drag to add files</span>}
<FileDragAndDropPreviewIterator
files={this.props.filesToUpload}
handleDeleteFile={this.handleDeleteFile}/>
<input
multiple
ref="fileinput"
type="file"
style={{
display: 'none',
height: 0,
width: 0
}}
onChange={this.handleDrop} />
</div>
);
}
});
module.exports = FileDragAndDrop;

View File

@ -0,0 +1,52 @@
import React from 'react';
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
let FileDragAndDropPreview = React.createClass({
propsTypes: {
file: React.PropTypes.shape({
url: React.PropTypes.string,
type: React.PropTypes.string
}).isRequired,
handleDeleteFile: React.PropTypes.func
},
handleDeleteFile(event) {
event.preventDefault();
event.stopPropagation();
// handleDeleteFile is optional, so if its not submitted,
// don't run it
if(this.props.handleDeleteFile) {
this.props.handleDeleteFile(this.props.file.id);
}
},
render() {
let previewElement;
// Decide wether an image or a placeholder picture should be displayed
if(this.props.file.type.split('/')[0] === 'image') {
previewElement = (<FileDragAndDropPreviewImage
progress={this.props.file.progress}
url={this.props.file.url}/>);
} else {
previewElement = (<FileDragAndDropPreviewOther
progress={this.props.file.progress}
type={this.props.file.type.split('/')[1]}/>);
}
return (
<div
className="file-drag-and-drop-position"
onClick={this.handleDeleteFile}>
{previewElement}
</div>
);
}
});
export default FileDragAndDropPreview;

View File

@ -0,0 +1,26 @@
import React from 'react';
import ProgressBar from 'react-progressbar';
let FileDragAndDropPreviewImage = React.createClass({
propTypes: {
progress: React.PropTypes.number,
url: React.PropTypes.string
},
render() {
let imageStyle = {
backgroundImage: 'url("' + this.props.url + '")',
backgroundSize: 'cover'
};
return (
<div
className="file-drag-and-drop-preview-image"
style={imageStyle}>
<ProgressBar completed={this.props.progress} color="black"/>
</div>
);
}
});
export default FileDragAndDropPreviewImage;

View File

@ -0,0 +1,31 @@
import React from 'react';
import FileDragAndDropPreview from './file_drag_and_drop_preview';
let FileDragAndDropPreviewIterator = React.createClass({
propTypes: {
files: React.PropTypes.array,
handleDeleteFile: React.PropTypes.func
},
render() {
if(this.props.files) {
return (
<div>
{this.props.files.map((file, i) => {
return (
<FileDragAndDropPreview
key={i}
file={file}
handleDeleteFile={this.props.handleDeleteFile}/>
);
})}
</div>
);
} else {
return null;
}
}
});
export default FileDragAndDropPreviewIterator;

View File

@ -0,0 +1,25 @@
import React from 'react';
import ProgressBar from 'react-progressbar';
let FileDragAndDropPreviewOther = React.createClass({
propTypes: {
type: React.PropTypes.string,
progress: React.PropTypes.number
},
render() {
return(
<div
className="file-drag-and-drop-preview">
<ProgressBar completed={this.props.progress} color="black"/>
<div className="file-drag-and-drop-preview-table-wrapper">
<div className="file-drag-and-drop-preview-other">
<span>{'.' + this.props.type}</span>
</div>
</div>
</div>
);
}
});
export default FileDragAndDropPreviewOther;

View File

@ -0,0 +1,306 @@
'use strict';
import React from 'react/addons';
import promise from 'es6-promise';
promise.polyfill();
import fetch from 'isomorphic-fetch';
import fineUploader from 'fineUploader';
import FileDragAndDrop from './file_drag_and_drop';
var ReactS3FineUploader = React.createClass({
propTypes: {
keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string,
fileClass: React.PropTypes.string
}),
createBlobRoutine: React.PropTypes.shape({
url: React.PropTypes.string
}),
handleChange: React.PropTypes.func,
autoUpload: React.PropTypes.bool,
debug: React.PropTypes.bool,
objectProperties: React.PropTypes.shape({
acl: React.PropTypes.string
}),
request: React.PropTypes.shape({
endpoint: React.PropTypes.string,
accessKey: React.PropTypes.string,
params: React.PropTypes.shape({
csrfmiddlewaretoken: React.PropTypes.string
})
}),
signature: React.PropTypes.shape({
endpoint: React.PropTypes.string
}),
uploadSuccess: React.PropTypes.shape({
method: React.PropTypes.string,
endpoint: React.PropTypes.string,
params: React.PropTypes.shape({
isBrowserPreviewCapable: React.PropTypes.any, // maybe fix this later
bitcoin_ID_noPrefix: React.PropTypes.string
})
}),
cors: React.PropTypes.shape({
expected: React.PropTypes.bool
}),
chunking: React.PropTypes.shape({
enabled: React.PropTypes.bool
}),
resume: React.PropTypes.shape({
enabled: React.PropTypes.bool
}),
deleteFile: React.PropTypes.shape({
enabled: React.PropTypes.bool,
method: React.PropTypes.string,
endpoint: React.PropTypes.string
}),
session: React.PropTypes.shape({
endpoint: React.PropTypes.bool
}),
validation: React.PropTypes.shape({
itemLimit: React.PropTypes.number,
sizeLimit: React.PropTypes.string
}),
messages: React.PropTypes.shape({
unsupportedBrowser: React.PropTypes.string
}),
formatFileName: React.PropTypes.func,
multiple: React.PropTypes.bool,
retry: React.PropTypes.shape({
enableAuto: React.PropTypes.bool
})
},
getInitialState() {
return {
filesToUpload: [],
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig())
};
},
propsToConfig() {
let objectProperties = this.props.objectProperties;
objectProperties.key = this.requestKey;
return {
autoUpload: this.props.autoUpload,
debug: this.props.debug,
objectProperties: objectProperties, // do a special key handling here
request: this.props.request,
signature: this.props.signature,
uploadSuccess: this.props.uploadSuccess,
cors: this.props.cors,
chunking: this.props.chunking,
resume: this.props.resume,
deleteFile: this.props.deleteFile,
session: this.props.session,
validation: this.props.validation,
messages: this.props.messages,
formatFileName: this.props.formatFileName,
multiple: this.props.multiple,
retry: this.props.retry,
callbacks: {
onSubmit: this.onSubmit,
onComplete: this.onComplete,
onDelete: this.onDelete,
onSessionRequestComplete: this.onSessionRequestComplete,
onProgress: this.onProgress,
onRetry: this.onRetry,
onAutoRetry: this.onAutoRetry,
onManualRetry: this.onManualRetry,
onDeleteComplete: this.onDeleteComplete
}
};
},
getCookie(name) {
console.log(document.cookie);
let value = '; ' + document.cookie;
let parts = value.split('; ' + name + '=');
if (parts.length === 2) {
return parts.pop().split(';').shift();
}
},
requestKey(fileId) {
let filename = this.state.uploader.getName(fileId);
let defer = new fineUploader.Promise();
fetch(this.props.keyRoutine.url, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': this.getCookie('csrftoken')
},
credentials: 'include',
body: JSON.stringify({
'filename': filename,
'file_class': 'digitalwork'
})
})
.then((res) => {
return res.json();
})
.then((res) =>{
defer.success(res.key);
})
.catch((err) => {
console.error(err);
});
return defer;
},
/* FineUploader specific callback function handlers */
onSubmit() {
console.log('submit');
},
onComplete(id) {
let files = this.state.filesToUpload;
files[id].status = 'upload successful';
files[id].key = this.state.uploader.getKey(id);
let newState = React.addons.update(this.state, {
filesToUpload: { $set: files }
});
this.setState(newState);
this.createBlob(files[id]);
this.props.handleChange();
console.log('completed ' + files[id].name);
},
createBlob(file) {
let defer = new fineUploader.Promise();
fetch(this.props.createBlobRoutine.url, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': this.getCookie('csrftoken')
},
credentials: 'include',
body: JSON.stringify({
'filename': file.name,
'key': file.key
})
})
.then((res) => {
return res.json();
})
.then((res) =>{
defer.success(res.key);
})
.catch((err) => {
console.error(err);
});
return defer;
},
onRetry() {
console.log('retry');
},
onAutoRetry() {
console.log('autoretry');
},
onManualRetry() {
console.log('manualretry');
},
onDelete() {
console.log('delete');
},
onCancel() {
console.log('cancel');
},
onSessionRequestComplete() {
console.log('sessionrequestcomplete');
},
onDeleteComplete(id, xhr, isError) {
if(isError) {
// also, sync files from state with the ones from fineuploader
let filesToUpload = JSON.parse(JSON.stringify(this.state.filesToUpload));
// splice because I can
filesToUpload.splice(id, 1);
// set state
this.setState({
filesToUpload: React.addons.update(this.state.filesToUpload, {$set: filesToUpload})
});
} else {
console.log(id);
// TODO: add global notification
}
},
onProgress(id, name, uploadedBytes, totalBytes) {
var newState = React.addons.update(this.state, {
filesToUpload: { [id]: {
progress: { $set: (uploadedBytes / totalBytes) * 100} }
}
});
this.setState(newState);
},
handleDeleteFile(fileId) {
// delete file from server
this.state.uploader.deleteFile(fileId);
// this is being continues in onDeleteFile, as
// fineuploaders deleteFile does not return a correct callback or
// promise
},
handleUploadFile(files) {
this.state.uploader.addFiles(files);
let oldFiles = this.state.filesToUpload;
let oldAndNewFiles = this.state.uploader.getUploads();
// Add fineuploader specific information to new files
for(let i = 0; i < oldAndNewFiles.length; i++) {
for(let j = 0; j < files.length; j++) {
if(oldAndNewFiles[i].originalName === files[j].name) {
oldAndNewFiles[i].progress = 0;
oldAndNewFiles[i].type = files[j].type;
oldAndNewFiles[i].url = URL.createObjectURL(files[j]);
}
}
}
// and re-add fineuploader specific information for old files as well
for(let i = 0; i < oldAndNewFiles.length; i++) {
for(let j = 0; j < oldFiles.length; j++) {
if(oldAndNewFiles[i].originalName === oldFiles[j].name) {
oldAndNewFiles[i].progress = oldFiles[j].progress;
oldAndNewFiles[i].type = oldFiles[j].type;
oldAndNewFiles[i].url = oldFiles[j].url;
}
}
}
let newState = React.addons.update(this.state, {
filesToUpload: { $set: oldAndNewFiles }
});
this.setState(newState);
},
render() {
return (
<FileDragAndDrop
onDrop={this.handleUploadFile}
filesToUpload={this.state.filesToUpload}
handleDeleteFile={this.handleDeleteFile}/>
);
}
});
export default ReactS3FineUploader;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2,19 +2,21 @@
import React from 'react'; import React from 'react';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
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 MediaPlayer from './ascribe_media/media_player'; import MediaPlayer from './ascribe_media/media_player';
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin'; import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import PersonalNoteForm from './ascribe_forms/form_note_personal'; import Form from './ascribe_forms/form';
import EditionNoteForm from './ascribe_forms/form_note_edition'; import Property from './ascribe_forms/property';
import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable';
import PieceExtraDataForm from './ascribe_forms/form_piece_extradata'; import PieceExtraDataForm from './ascribe_forms/form_piece_extradata';
import RequestActionForm from './ascribe_forms/form_request_action'; import RequestActionForm from './ascribe_forms/form_request_action';
@ -25,7 +27,8 @@ import AclButtonList from './ascribe_buttons/acl_button_list';
import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationActions from '../actions/global_notification_actions';
import classNames from 'classnames'; import requests from '../utils/requests';
import apiUrls from '../constants/api_urls';
/** /**
* This is the component that implements display-specific functionality * This is the component that implements display-specific functionality
@ -92,64 +95,72 @@ let Edition = React.createClass({
<EditionSummary <EditionSummary
edition={this.props.edition} /> edition={this.props.edition} />
<CollapsibleEditionDetails <CollapsibleParagraph
title="Personal Note (private)" title="Notes"
show={this.state.currentUser.username && true || false}> show={(this.state.currentUser.username && true || false) ||
(this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note)}>
<EditionPersonalNote <EditionPersonalNote
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.loadEdition} handleSuccess={this.props.loadEdition}
edition={this.props.edition}/> edition={this.props.edition}/>
</CollapsibleEditionDetails>
<CollapsibleEditionDetails
title="Edition Note (public)"
show={this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note}>
<EditionPublicEditionNote <EditionPublicEditionNote
handleSuccess={this.props.loadEdition} handleSuccess={this.props.loadEdition}
edition={this.props.edition}/> edition={this.props.edition}/>
</CollapsibleEditionDetails> </CollapsibleParagraph>
<CollapsibleEditionDetails <CollapsibleParagraph
title="Further Details (all editions)" title="Further Details (all editions)"
show={this.props.edition.acl.indexOf('edit') > -1 || Object.keys(this.props.edition.extra_data).length > 0}> show={this.props.edition.acl.indexOf('edit') > -1 || Object.keys(this.props.edition.extra_data).length > 0}>
<EditionFurtherDetails <EditionFurtherDetails
handleSuccess={this.props.loadEdition} handleSuccess={this.props.loadEdition}
edition={this.props.edition}/> edition={this.props.edition}/>
</CollapsibleEditionDetails> </CollapsibleParagraph>
<CollapsibleEditionDetails <CollapsibleParagraph
title="Provenance/Ownership History" title="Provenance/Ownership History"
show={this.props.edition.ownership_history && this.props.edition.ownership_history.length > 0}> show={this.props.edition.ownership_history && this.props.edition.ownership_history.length > 0}>
<EditionDetailHistoryIterator <EditionDetailHistoryIterator
history={this.props.edition.ownership_history} /> history={this.props.edition.ownership_history} />
</CollapsibleEditionDetails> </CollapsibleParagraph>
<CollapsibleEditionDetails <CollapsibleParagraph
title="Consignment History" title="Consignment History"
show={this.props.edition.consign_history && this.props.edition.consign_history.length > 0}> show={this.props.edition.consign_history && this.props.edition.consign_history.length > 0}>
<EditionDetailHistoryIterator <EditionDetailHistoryIterator
history={this.props.edition.consign_history} /> history={this.props.edition.consign_history} />
</CollapsibleEditionDetails> </CollapsibleParagraph>
<CollapsibleEditionDetails <CollapsibleParagraph
title="Loan History" title="Loan History"
show={this.props.edition.loan_history && this.props.edition.loan_history.length > 0}> show={this.props.edition.loan_history && this.props.edition.loan_history.length > 0}>
<EditionDetailHistoryIterator <EditionDetailHistoryIterator
history={this.props.edition.loan_history} /> history={this.props.edition.loan_history} />
</CollapsibleEditionDetails> </CollapsibleParagraph>
<CollapsibleEditionDetails <CollapsibleParagraph
title="SPOOL Details"> title="SPOOL Details">
<EditionDetailProperty <Form >
label="Artwork ID" <Property
value={bitcoinIdValue} /> name='artwork_id'
<EditionDetailProperty label="Artwork ID"
label="Hash of Artwork, title, etc" editable={false}>
value={hashOfArtwork} /> <pre className="ascribe-pre">{bitcoinIdValue}</pre>
<EditionDetailProperty </Property>
label="Owned by SPOOL address" <Property
value={ownerAddress} /> name='hash_of_artwork'
</CollapsibleEditionDetails> label="Hash of Artwork, title, etc"
editable={false}>
<pre className="ascribe-pre">{hashOfArtwork}</pre>
</Property>
<Property
name='owner_address'
label="Owned by SPOOL address"
editable={false}>
<pre className="ascribe-pre">{ownerAddress}</pre>
</Property>
<hr />
</Form>
</CollapsibleParagraph>
</Col> </Col>
</Row> </Row>
); );
@ -229,85 +240,6 @@ let EditionSummary = React.createClass({
} }
}); });
let CollapsibleEditionDetails = React.createClass({
propTypes: {
title: React.PropTypes.string,
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
show: React.PropTypes.bool,
iconName: React.PropTypes.string
},
getDefaultProps() {
return {
show: true
};
},
render() {
if(this.props.show) {
return (
<div className="ascribe-detail-header">
<CollapsibleParagraph
title={this.props.title}
iconName={this.props.iconName}>
{this.props.children}
</CollapsibleParagraph>
</div>
);
} else {
return null;
}
}
});
const CollapsibleParagraph = React.createClass({
propTypes: {
title: React.PropTypes.string,
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
iconName: React.PropTypes.string
},
mixins: [CollapsibleMixin],
getCollapsibleDOMNode(){
return React.findDOMNode(this.refs.panel);
},
getCollapsibleDimensionValue(){
return React.findDOMNode(this.refs.panel).scrollHeight;
},
onHandleToggle(e){
e.preventDefault();
this.setState({expanded: !this.state.expanded});
},
render() {
let styles = this.getCollapsibleClassSet();
let text = this.isExpanded() ? '-' : '+';
let icon = this.props.iconName ? (<Glyphicon style={{fontSize: '.75em'}} glyph={this.props.iconName} />) : null;
return (
<div className="ascribe-edition-collapsible-wrapper">
<div onClick={this.onHandleToggle}>
<span>{text} {icon} {this.props.title} </span>
</div>
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}>
{this.props.children}
</div>
</div>
);
}
});
let EditionDetailProperty = React.createClass({ let EditionDetailProperty = React.createClass({
propTypes: { propTypes: {
@ -372,6 +304,7 @@ let EditionDetailHistoryIterator = React.createClass({
let EditionPersonalNote = React.createClass({ let EditionPersonalNote = React.createClass({
propTypes: { propTypes: {
edition: React.PropTypes.object, edition: React.PropTypes.object,
currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
showNotification(){ showNotification(){
@ -379,21 +312,35 @@ let EditionPersonalNote = React.createClass({
let notification = new GlobalNotificationModel('Private note saved', 'success'); let notification = new GlobalNotificationModel('Private note saved', 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
return ( if (this.props.currentUser.username && true || false) {
<Row> return (
<Col md={12} className="ascribe-edition-personal-note"> <Form
<PersonalNoteForm url={apiUrls.note_notes}
editable={true} handleSuccess={this.showNotification}>
handleSuccess={this.showNotification} <Property
editions={[this.props.edition]} /> name='note'
</Col> label='Personal note (private)'
</Row> editable={true}>
); <InputTextAreaToggable
rows={3}
editable={true}
defaultValue={this.props.edition.note_from_user}
placeholder='Enter a personal note...'
required/>
</Property>
<Property hidden={true} name='bitcoin_id'>
<input defaultValue={this.props.edition.bitcoin_id}/>
</Property>
<hr />
</Form>
);
}
return null;
} }
}); });
let EditionPublicEditionNote = React.createClass({ let EditionPublicEditionNote = React.createClass({
propTypes: { propTypes: {
edition: React.PropTypes.object, edition: React.PropTypes.object,
@ -405,16 +352,31 @@ let EditionPublicEditionNote = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
return ( let isEditable = this.props.edition.acl.indexOf('edit') > -1;
<Row> if (this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note){
<Col md={12} className="ascribe-edition-personal-note"> return (
<EditionNoteForm <Form
editable={this.props.edition.acl.indexOf('edit') > -1} url={apiUrls.note_edition}
handleSuccess={this.showNotification} handleSuccess={this.showNotification}>
editions={[this.props.edition]} /> <Property
</Col> name='note'
</Row> label='Edition note (public)'
); editable={isEditable}>
<InputTextAreaToggable
rows={3}
editable={isEditable}
defaultValue={this.props.edition.public_note}
placeholder='Enter a public note for this edition...'
required/>
</Property>
<Property hidden={true} name='bitcoin_id'>
<input defaultValue={this.props.edition.bitcoin_id}/>
</Property>
<hr />
</Form>
);
}
return null;
} }
}); });
@ -440,19 +402,19 @@ let EditionFurtherDetails = React.createClass({
title='Artist Contact Info' title='Artist Contact Info'
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
editable={editable} editable={editable}
editions={[this.props.edition]} /> edition={this.props.edition} />
<PieceExtraDataForm <PieceExtraDataForm
name='display_instructions' name='display_instructions'
title='Display Instructions' title='Display Instructions'
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
editable={editable} editable={editable}
editions={[this.props.edition]} /> edition={this.props.edition} />
<PieceExtraDataForm <PieceExtraDataForm
name='technology_details' name='technology_details'
title='Technology Details' title='Technology Details'
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
editable={editable} editable={editable}
editions={[this.props.edition]} /> edition={this.props.edition} />
</Col> </Col>
</Row> </Row>
); );

View File

@ -39,6 +39,7 @@ let Header = React.createClass({
handleLogout(){ handleLogout(){
UserActions.logoutCurrentUser(); UserActions.logoutCurrentUser();
Alt.flush(); Alt.flush();
this.transitionTo('login');
}, },
onChange(state) { onChange(state) {
this.setState(state); this.setState(state);
@ -71,7 +72,6 @@ let Header = React.createClass({
<Navbar brand={brand} toggleNavKey={0}> <Navbar brand={brand} toggleNavKey={0}>
<CollapsibleNav eventKey={0}> <CollapsibleNav eventKey={0}>
<Nav navbar />
<Nav navbar right> <Nav navbar right>
{account} {account}
{signup} {signup}

View File

@ -15,18 +15,99 @@ import Property from './ascribe_forms/property';
import apiUrls from '../constants/api_urls'; import apiUrls from '../constants/api_urls';
import ReactS3FineUploader from 'ReactS3FineUploader'; import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
import DatePicker from 'react-datepicker/dist/react-datepicker';
let RegisterPiece = React.createClass( { let RegisterPiece = React.createClass( {
render() { mixins: [Router.Navigation],
getInitialState(){
return {digital_work_key: null};
},
handleSuccess(){
let notification = new GlobalNotificationModel('Login successsful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
},
getFormData(){
let data = {};
for (let ref in this.refs.form.refs){
data[this.refs.form.refs[ref].props.name] = this.refs.form.refs[ref].state.value;
}
data.digital_work_key = this.state.digital_work_key;
return data;
},
handleChange(){
this.setState({digital_work_key: this.refs.uploader.refs.fineuploader.state.filesToUpload[0].key})
},
render() {
let buttons = null;
if (this.refs.uploader && this.refs.uploader.refs.fineuploader.state.filesToUpload[0].status === 'upload successful'){
buttons = (
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
Register your artwork
</button>);
}
return ( return (
<div className="row ascribe-row"> <div className="row ascribe-row">
<div className="col-md-6"> <div className="col-md-5">
<FileUploader /> <FileUploader
ref='uploader'
handleChange={this.handleChange}/>
<br />
</div> </div>
<div className="col-md-6"> <div className="col-md-7">
<LoginForm /> <h3 style={{'marginTop': 0}}>Lock down title</h3>
<Form
ref='form'
url={apiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.handleSuccess}
buttons={buttons}
spinner={
<button 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" />
</button>
}>
<Property
name='artist_name'
label="Artist Name">
<input
type="text"
placeholder="The name of the creator"
required/>
</Property>
<Property
name='title'
label="Artwork title">
<input
type="text"
placeholder="The title of the artwork"
required/>
</Property>
<Property
name='date_created'
label="Year Created">
<input
type="number"
placeholder="Year Created (e.g. 2015)"
min={0}
required/>
</Property>
<Property
name='num_editions'
label="Number of editions">
<input
type="number"
placeholder="Specify the number of unique editions for this artwork"
min={1}
required/>
</Property>
<hr />
</Form>
</div> </div>
</div> </div>
); );
@ -36,13 +117,17 @@ let RegisterPiece = React.createClass( {
let FileUploader = React.createClass( { let FileUploader = React.createClass( {
render() { render() {
return ( return (
<ReactS3FineUploader <ReactS3FineUploader
ref='fineuploader'
keyRoutine={{ keyRoutine={{
url: AppConstants.serverUrl + 's3/key/', url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork' fileClass: 'digitalwork'
}} }}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
handleChange={this.props.handleChange}
autoUpload={true} autoUpload={true}
debug={false} debug={false}
objectProperties={{ objectProperties={{
@ -94,60 +179,46 @@ let FileUploader = React.createClass( {
} }
return name; return name;
}} }}
multiple={true}/> multiple={false}/>
); );
} }
}); });
let LoginForm = React.createClass({
mixins: [Router.Navigation],
handleSuccess(){
let notification = new GlobalNotificationModel('Login successsful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
let InputDate = React.createClass({
propTypes: {
placeholderText: React.PropTypes.string
}, },
render() {
getInitialState() {
return {
value: null,
value_formatted: null
};
},
handleChange(date) {
this.setState({
value: date,
value_formatted: date.format('YYYY')});
let event = document.createEvent('HTMLEvents');
event.initEvent('click', false, true);
document.dispatchEvent(event);
event.target.value = date;
this.props.onChange(event);
},
render: function () {
return ( return (
<Form <DatePicker
url={apiUrls.users_login} key="example2"
handleSuccess={this.handleSuccess} dateFormat="YYYY"
buttons={ selected={this.state.value}
<button type="submit" className="btn ascribe-btn ascribe-btn-login"> onChange={this.handleChange}
Log in to ascribe onBlur={this.props.onBlur}
</button>} placeholderText={this.props.placeholderText}/>
spinner={
<button 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" />
</button>
}>
<Property
name='email'
label="Email">
<input
type="email"
placeholder="Enter your email"
autoComplete="on"
required/>
</Property>
<Property
name='password'
label="Password">
<input
type="password"
placeholder="Enter your password"
autoComplete="on"
required/>
</Property>
<hr />
<div className="ascribe-login-text">
Not an ascribe user&#63; Sign up...<br/>
Forgot my password&#63; Rescue me...
</div>
</Form>
); );
} }
}); });
export default RegisterPiece; export default RegisterPiece;

View File

@ -9,6 +9,9 @@ import UserStore from '../stores/user_store';
import WalletSettingsActions from '../actions/wallet_settings_actions'; import WalletSettingsActions from '../actions/wallet_settings_actions';
import WalletSettingsStore from '../stores/wallet_settings_store'; import WalletSettingsStore from '../stores/wallet_settings_store';
import ApplicationActions from '../actions/application_actions';
import ApplicationStore from '../stores/application_store';
import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationActions from '../actions/global_notification_actions';
@ -28,6 +31,7 @@ let SettingsContainer = React.createClass({
<div> <div>
<AccountSettings /> <AccountSettings />
<BitcoinWalletSettings /> <BitcoinWalletSettings />
<APISettings />
</div> </div>
); );
} }
@ -54,47 +58,65 @@ let AccountSettings = React.createClass({
handleSuccess(){ handleSuccess(){
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
let notification = new GlobalNotificationModel('username succesfully updated', 'success', 10000); let notification = new GlobalNotificationModel('username succesfully updated', 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.currentUser.username) { if (this.state.currentUser.username) {
return ( content = (
<CollapsibleParagraph <Form
title="Account" url={apiUrls.users_username}
show={true} handleSuccess={this.handleSuccess}>
defaultExpanded={true}> <Property
<Form name='username'
url={apiUrls.users_username} label="Username">
handleSuccess={this.handleSuccess}> <input
<Property type="text"
name='username' defaultValue={this.state.currentUser.username}
label="Username"> placeholder="Enter your username"
<input required/>
type="text" </Property>
defaultValue={this.state.currentUser.username} <Property
placeholder="Enter your username" name='email'
required/> label="Email"
</Property> editable={false}>
<Property <input
name='email' type="text"
label="Email" defaultValue={this.state.currentUser.email}
editable={false}> placeholder="Enter your username"
<input required/>
type="text" </Property>
defaultValue={this.state.currentUser.email} <hr />
placeholder="Enter your username" </Form>
required/>
</Property>
</Form>
</CollapsibleParagraph>
);
}
else {
return (
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
); );
} }
return (
<CollapsibleParagraph
title="Account"
show={true}
defaultExpanded={true}>
{content}
<Form
url={AppConstants.serverUrl + 'api/users/set_language/'}>
<Property
name='language'
label="Choose your Language"
editable={true}>
<select id="select-lang" name="language">
<option value="fr">
Fran&ccedil;ais
</option>
<option value="en"
selected="selected">
English
</option>
</select>
</Property>
<hr />
</Form>
</CollapsibleParagraph>
);
} }
}); });
@ -120,42 +142,33 @@ let BitcoinWalletSettings = React.createClass({
}, },
render() { render() {
if (this.state.walletSettings.btc_public_key) { let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
return ( if (this.state.walletSettings.btc_public_key) {
<CollapsibleParagraph content = (
title="Crypto Wallet" <Form >
show={true} <Property
defaultExpanded={true}> name='btc_public_key'
<Form label="Bitcoin public key"
url={apiUrls.users_username} editable={false}>
handleSuccess={this.handleSuccess}> <pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
<Property </Property>
name='btc_public_key' <Property
label="Bitcoin public key" name='btc_root_address'
editable={false}> label="Root Address"
<input editable={false}>
type="text" <pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
defaultValue={this.state.walletSettings.btc_public_key} </Property>
placeholder="Enter your username" <hr />
required/> </Form>);
</Property>
<Property
name='btc_root_address'
label="Root Address"
editable={false}>
<input
type="text"
defaultValue={this.state.walletSettings.btc_root_address}/>
</Property>
</Form>
</CollapsibleParagraph>
);
}
else {
return (
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
);
} }
return (
<CollapsibleParagraph
title="Crypto Wallet"
show={true}
defaultExpanded={true}>
{content}
</CollapsibleParagraph>
);
} }
}); });
@ -177,18 +190,88 @@ let ContractSettings = React.createClass({
}); });
let APISettings = React.createClass({ let APISettings = React.createClass({
getInitialState() {
propTypes: { return ApplicationStore.getState();
currentUser: React.PropTypes.object
}, },
render() { componentDidMount() {
ApplicationStore.listen(this.onChange);
ApplicationActions.fetchApplication();
},
componentWillUnmount() {
ApplicationStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
handleCreateSuccess: function(){
ApplicationActions.fetchApplication();
let notification = new GlobalNotificationModel('Application successfully created', 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
handleTokenRefresh: function(event){
let applicationName = event.target.getAttribute('data-id');
ApplicationActions.refreshApplicationToken(applicationName);
let notification = new GlobalNotificationModel('Token refreshed', 'success', 2000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.applications.length > -1) {
content = this.state.applications.map(function(app) {
return (
<Property
name={app.name}
label={app.name}>
<div className="row-same-height">
<div className="col-xs-6 col-xs-height col-middle">
{'Bearer ' + app.bearer_token.token}
</div>
<div className="col-xs-6 col-xs-height">
<button
className="pull-right btn btn-default btn-sm"
onClick={this.handleTokenRefresh}
data-id={app.name}>
REFRESH
</button>
</div>
</div>
</Property>);
}, this);
content = (
<div>
<Form>
{content}
<hr />
</Form>
</div>);
}
return ( return (
<div> <CollapsibleParagraph
<div>Username: {this.props.currentUser.username}</div> title="API Integration"
<div>Email: {this.props.currentUser.email}</div> show={true}
</div> defaultExpanded={true}>
<Form
url={apiUrls.applications}
handleSuccess={this.handleCreateSuccess}>
<Property
name='name'
label='Application Name'>
<input
type="text"
placeholder="Enter the name of your app"
required/>
</Property>
<hr />
</Form>
<pre>
Usage: curl &lt;url&gt; -H 'Authorization: Bearer &lt;token&gt;'
</pre>
{content}
</CollapsibleParagraph>
); );
} }
}); });

View File

@ -3,6 +3,9 @@
import AppConstants from './application_constants'; import AppConstants from './application_constants';
let apiUrls = { let apiUrls = {
'applications': AppConstants.apiEndpoint + 'applications/',
'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/',
'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/',
@ -15,7 +18,7 @@ let apiUrls = {
'ownership_loans': AppConstants.apiEndpoint + 'ownership/loans/', 'ownership_loans': AppConstants.apiEndpoint + 'ownership/loans/',
'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/', 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/',
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/',
'ownership_shares_mail': AppConstants.apiEndpoint + 'ownership/shares/mail/', 'ownership_shares': AppConstants.apiEndpoint + 'ownership/shares/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',
'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/', 'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/',

View File

@ -0,0 +1,17 @@
'use strict';
import requests from '../utils/requests';
let ApplicationFetcher = {
/**
* Fetch the registered applications of a user from the API.
*/
fetch() {
return requests.get('applications');
},
refreshToken(applicationName) {
return requests.post('application_token_refresh', { body: {'name': applicationName}});
}
};
export default ApplicationFetcher;

View File

@ -19,7 +19,6 @@ let Route = Router.Route;
let Redirect = Router.Redirect; let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl; let baseUrl = AppConstants.baseUrl;
let routes = ( let routes = (
<Route name="app" path={baseUrl} handler={AscribeApp}> <Route name="app" path={baseUrl} handler={AscribeApp}>
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
@ -30,8 +29,8 @@ let routes = (
<Route name="register_piece" path="register_piece" handler={RegisterPiece} /> <Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Redirect from={baseUrl} to="pieces" /> <Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="pieces" /> <Redirect from={baseUrl + '/'} to="login" />
</Route> </Route>
); );

View File

@ -0,0 +1,18 @@
'use strict';
import alt from '../alt';
import ApplicationActions from '../actions/application_actions';
class ApplicationStore {
constructor() {
this.applications = {};
this.bindActions(ApplicationActions);
}
onUpdateApplications(applications) {
this.applications = applications;
}
}
export default alt.createStore(ApplicationStore, 'ApplicationStore');

1
node_modules/react-s3-fineuploader generated vendored

@ -1 +0,0 @@
Subproject commit e2890cf3d28d010abc04e0de3b218afe8c949596

View File

@ -13,8 +13,7 @@
"start": "node server.js" "start": "node server.js"
}, },
"browser": { "browser": {
"fineUploader": "./node_modules/react-s3-fineuploader/vendor/s3.fine-uploader.js", "fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js"
"ReactS3FineUploader": "./node_modules/react-s3-fineuploader/react_s3_fine_uploader.js"
}, },
"browserify-shim": { "browserify-shim": {
"fineUploader": "qq" "fineUploader": "qq"
@ -60,7 +59,7 @@
"gulp-notify": "^2.2.0", "gulp-notify": "^2.2.0",
"gulp-sass": "^2.0.1", "gulp-sass": "^2.0.1",
"gulp-sourcemaps": "^1.5.2", "gulp-sourcemaps": "^1.5.2",
"gulp-template": "^3.0.0", "gulp-template": "~3.0.0",
"gulp-uglify": "^1.2.0", "gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4", "gulp-util": "^3.0.4",
"harmonize": "^1.4.2", "harmonize": "^1.4.2",
@ -71,6 +70,7 @@
"react": "^0.13.2", "react": "^0.13.2",
"react-bootstrap": "~0.22.6", "react-bootstrap": "~0.22.6",
"react-datepicker": "~0.8.0", "react-datepicker": "~0.8.0",
"react-progressbar": "^1.1.0",
"react-router": "^0.13.3", "react-router": "^0.13.3",
"react-textarea-autosize": "^2.2.3", "react-textarea-autosize": "^2.2.3",
"reactify": "^1.1.0", "reactify": "^1.1.0",

View File

@ -1,6 +1,3 @@
.ascribe-edition-details hr {
border-top: 1px solid #616161;
}
.ascribe-edition-personal-note > textarea { .ascribe-edition-personal-note > textarea {
margin-bottom: 1em; margin-bottom: 1em;
@ -12,16 +9,23 @@
} }
.ascribe-edition-collapsible-wrapper > div:first-child { .ascribe-edition-collapsible-wrapper > div:first-child {
width:100%; width: 100%;
cursor:pointer; cursor: pointer;
} background-color: #EEE;
padding: 10px;
border: 1px solid #CCC;
}
.ascribe-edition-collapsible-wrapper > div > span { .ascribe-edition-collapsible-wrapper > div > span {
font-size:1.3em; font-size:1.3em;
margin-right: .5em; margin-right: .5em;
}
.ascribe-edition-collapsible-wrapper > div > span:nth-child(2) {
font-size: 0.9em;
} }
.ascribe-edition-collapible-content { .ascribe-edition-collapible-content {
width:100%; width:100%;
margin-top: 1em; margin-top: 1em;
} }

View File

@ -1,7 +1,13 @@
$break-small: 764px; $break-small: 764px;
$break-medium: 991px;
.ascribe-row { .ascribe-row {
max-width: 600px; @media screen and (max-width: $break-medium) {
max-width: 600px;
}
@media screen and (min-width: $break-medium) {
max-width: 1200px;
}
margin: 0 auto margin: 0 auto
} }

View File

@ -1,3 +1,4 @@
.ascribe-settings-wrapper { .ascribe-settings-wrapper {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -10,11 +11,18 @@
&div:last-of-type { &div:last-of-type {
border-bottom: 1px solid rgba(0,0,0,.2); border-bottom: 1px solid rgba(0,0,0,.2);
} }
&:hover{
border-left: 3px solid rgba(2, 182, 163, 0.4);
}
}
.is-hidden{
display: none;
} }
.is-focused { .is-focused {
background-color: rgba(2, 182, 163, 0.05); background-color: rgba(2, 182, 163, 0.05);
border-left: 3px solid rgba(2, 182, 163, 1); border-left: 3px solid rgba(2, 182, 163, 1)!important;
} }
.is-error { .is-error {
@ -26,13 +34,14 @@
font-size: 0.9em; font-size: 0.9em;
margin-right: 1em; margin-right: 1em;
} }
span { input, textarea {
}
input {
color: #666; color: #666;
} }
} }
&:hover{
border-left: 3px solid rgba(169, 68, 66, 1);
}
} }
.is-fixed { .is-fixed {
@ -42,13 +51,15 @@
span { span {
cursor: default; cursor: default;
} }
input { input, div, pre, textarea {
cursor: default; cursor: default;
color: #666; color: #666;
} }
} }
} }
.ascribe-settings-property { .ascribe-settings-property {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
@ -60,15 +71,28 @@
cursor:pointer; cursor:pointer;
span { input, div, span, pre, textarea, select {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
span {
font-weight: normal; font-weight: normal;
font-size: 0.9em; font-size: 0.9em;
color: rgba(0,0,0,.7); color: rgba(0,0,0,.7);
} }
input { div {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; margin-top: 10px;
div {
padding-left: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
font-size: 1.1em;
cursor: default;
color: rgba(0, 0, 0, .7);
}
}
input, pre, textarea, select {
font-weight: 400; font-weight: 400;
font-size: 1.1em; font-size: 1.1em;
width:100%; width:100%;
@ -76,10 +100,12 @@
border: 0; border: 0;
background-color: rgba(0,0,0,0); background-color: rgba(0,0,0,0);
color: #38BAAD; color: #38BAAD;
padding-left: 0;
&:focus { &:focus {
border:0; border:0;
outline:0; outline:0;
box-shadow: none;
} }
&::selection { &::selection {
@ -89,4 +115,9 @@
} }
textarea{
color: #666;
margin-top: 1em;
padding: 0;
}
} }

View File

@ -10,12 +10,14 @@
.ascribe-pre{ .ascribe-pre{
word-break: break-word; word-break: break-word;
/* white-space: pre-wrap; */ white-space: pre-wrap;
white-space: -moz-pre-wrap; white-space: -moz-pre-wrap;
white-space: -pre-wrap; white-space: -pre-wrap;
white-space: -o-pre-wrap; white-space: -o-pre-wrap;
/* word-wrap: break-word; */
font-family: inherit; font-family: inherit;
text-align: justify; text-align: justify;
background-color: white; background-color: white;
border: none;
padding: 0;
margin:0;
} }

View File

@ -0,0 +1,64 @@
.file-drag-and-drop {
display: table-cell;
outline: 1px dashed #616161;
cursor: pointer;
vertical-align: middle;
text-align: center;
height:208px;
width: 672px;
background-color: #FAFAFA;
transition: .1s linear background-color;
}
.file-drag-and-drop:hover {
background-color: rgba(72, 218, 203, 0.2);
}
.file-drag-and-drop > span {
font-size: 1.5em;
}
.has-files {
text-align: left;
padding: 3em 0 0 0;
}
.file-drag-and-drop-position {
display: inline-block;
margin: 0 0 3em 3em;
float:left;
}
.file-drag-and-drop-preview-table-wrapper {
display: table;
height:94px;
width:104px;
}
.file-drag-and-drop-preview {
overflow:hidden;
cursor: default;
background-color: #EEEEEE;
border: 1px solid #616161;
}
.file-drag-and-drop-preview-image {
display: table;
height:104px;
width:104px;
overflow:hidden;
border: 1px solid #616161;
}
.file-drag-and-drop-preview-other {
display: table-cell;
text-align: center;
vertical-align: middle;
}
.file-drag-and-drop-preview-other span {
font-size: 1.1em;
display: block;
margin-top: -10px;
}

View File

@ -17,6 +17,7 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'ascribe_edition'; @import 'ascribe_edition';
@import 'ascribe_textarea'; @import 'ascribe_textarea';
@import 'ascribe_media_player'; @import 'ascribe_media_player';
@import 'ascribe_uploader';
@import 'ascribe-global-notification'; @import 'ascribe-global-notification';
@import 'ascribe_piece_register'; @import 'ascribe_piece_register';
@import 'offset_right'; @import 'offset_right';