1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-30 13:41:57 +02:00

finalize delete and cancel functionality

This commit is contained in:
Tim Daubenschütz 2015-06-29 11:01:45 +02:00
commit 1a9c82d683
18 changed files with 439 additions and 84 deletions

View File

@ -93,7 +93,8 @@ gulp.task('browser-sync', function() {
browserSync({
files: config.filesToWatch,
proxy: 'http://localhost:4000',
port: 3000
port: 3000,
browser: "chromium-browser"
});
});

36
js/actions/coa_actions.js Normal file
View File

@ -0,0 +1,36 @@
'use strict';
import alt from '../alt';
import CoaFetcher from '../fetchers/coa_fetcher';
class CoaActions {
constructor() {
this.generateActions(
'updateCoa'
);
}
fetchOne(id) {
CoaFetcher.fetchOne(id)
.then((res) => {
this.actions.updateCoa(res.coa);
console.log(res.coa);
})
.catch((err) => {
console.log(err);
});
}
create(edition) {
CoaFetcher.create(edition.bitcoin_id)
.then((res) => {
this.actions.updateCoa(res.coa);
console.log(res.coa);
})
.catch((err) => {
console.log(err);
});
}
}
export default alt.createActions(CoaActions);

View File

@ -107,7 +107,7 @@ var FileDragAndDrop = React.createClass({
},
render: function () {
let hasFiles = this.props.filesToUpload.length > 0;
let hasFiles = this.props.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0;
let className = hasFiles ? 'file-drag-and-drop has-files ' : 'file-drag-and-drop ';
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';

View File

@ -16,13 +16,17 @@ let FileDragAndDropPreviewIterator = React.createClass({
return (
<div>
{this.props.files.map((file, i) => {
return (
<FileDragAndDropPreview
key={i}
file={file}
handleDeleteFile={this.props.handleDeleteFile}
handleCancelFile={this.props.handleCancelFile}/>
);
if(file.status !== 'deleted' && file.status !== 'canceled') {
return (
<FileDragAndDropPreview
key={i}
file={file}
handleDeleteFile={this.props.handleDeleteFile}
handleCancelFile={this.props.handleCancelFile}/>
);
} else {
return null;
}
})}
</div>
);

View File

@ -6,6 +6,9 @@ import promise from 'es6-promise';
promise.polyfill();
import fetch from 'isomorphic-fetch';
import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
import fineUploader from 'fineUploader';
import FileDragAndDrop from './file_drag_and_drop';
@ -18,7 +21,8 @@ var ReactS3FineUploader = React.createClass({
propTypes: {
keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string,
fileClass: React.PropTypes.string
fileClass: React.PropTypes.string,
bitcoinId: React.PropTypes.string
}),
createBlobRoutine: React.PropTypes.shape({
url: React.PropTypes.string
@ -79,8 +83,67 @@ var ReactS3FineUploader = React.createClass({
})
},
getDefaultProps() {
return {
autoUpload: true,
debug: false,
objectProperties: {
acl: 'public-read',
bucket: 'ascribe0'
},
request: {
endpoint: 'https://ascribe0.s3.amazonaws.com',
accessKey: 'AKIAIVCZJ33WSCBQ3QDA'
},
uploadSuccess: {
params: {
isBrowserPreviewCapable: fineUploader.supportedFeatures.imagePreviews
}
},
signature: {
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
},
},
deleteFile: {
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
},
cors: {
expected: true,
sendCredentials: true
},
chunking: {
enabled: true
},
resume: {
enabled: true
},
retry: {
enableAuto: false
},
session: {
endpoint: null
},
messages: {
unsupportedBrowser: '<h3>Upload is not functional in IE7 as IE7 has no support for CORS!</h3>'
},
formatFileName: function(name){// fix maybe
if (name !== undefined && name.length > 26) {
name = name.slice(0, 15) + '...' + name.slice(-15);
}
return name;
},
multiple: false
};
},
getInitialState() {
console.log(this.props);
return {
filesToUpload: [],
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig())
@ -143,7 +206,8 @@ var ReactS3FineUploader = React.createClass({
credentials: 'include',
body: JSON.stringify({
'filename': filename,
'file_class': 'digitalwork'
'file_class': this.props.keyRoutine.fileClass,
'bitcoin_id': this.props.keyRoutine.bitcoinId
})
})
.then((res) => {
@ -270,7 +334,7 @@ var ReactS3FineUploader = React.createClass({
// If multiple set and user already uploaded its work,
// cancel upload
if(!this.props.multiple && this.state.filesToUpload.length > 0) {
if(!this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0) {
return;
}
@ -313,6 +377,7 @@ var ReactS3FineUploader = React.createClass({
}
}
// set the new file array
let newState = React.addons.update(this.state, {
filesToUpload: { $set: oldAndNewFiles }
});
@ -341,7 +406,7 @@ var ReactS3FineUploader = React.createClass({
handleDeleteFile={this.handleDeleteFile}
handleCancelFile={this.handleCancelFile}
multiple={this.props.multiple}
dropzoneInactive={!this.props.multiple && this.state.filesToUpload.length > 0} />
dropzoneInactive={!this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled').length > 0} />
);
}

View File

@ -0,0 +1,101 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable';
import apiUrls from '../constants/api_urls';
let CoaVerifyContainer = React.createClass({
mixins: [Router.Navigation],
render() {
return (
<div className="ascribe-login-wrapper">
<br/>
<div className="ascribe-login-text ascribe-login-header">
Verify your Certificate of Authenticity
</div>
<CoaVerifyForm />
<br />
<br />
ascribe is using the following public key for verification:
<br />
<pre>
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDddadqY31kKPFYk8PQA8BWSTbm
gaGf9KEYBALp2nWAJcwq80qBzGF+gfi0Z+yb4ooeKHl27GnuxZYValE1Z5ZujfeJ
TgO4li59ZMYiah8oXZp/OysrBwCvWw0PtWd8/D9Nc4PqyOz5gzEh6kFah5VsuAke
Znu2w7KmeLZ85SmwEQIDAQAB
-----END PUBLIC KEY-----
</pre>
</div>
);
}
});
let CoaVerifyForm = React.createClass({
mixins: [Router.Navigation],
handleSuccess(response){
let notification = null;
if (response.verdict){
notification = new GlobalNotificationModel('Certificate of Authenticity successfully verified', 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
}
},
render() {
return (
<div>
<Form
url={apiUrls.coa_verify}
handleSuccess={this.handleSuccess}
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login">
Verify your Certificate of Authenticity
</button>}
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='message'
label="Message">
<input
type="text"
placeholder="Copy paste the message on the bottom of your Certificate of Authenticity"
autoComplete="on"
name="username"
required/>
</Property>
<Property
name='signature'
label="Signature">
<InputTextAreaToggable
rows={3}
editable={true}
placeholder="Copy paste the signature on the bottom of your Certificate of Authenticity"
required/>
</Property>
<hr />
</Form>
</div>
);
}
});
export default CoaVerifyContainer;

View File

@ -1,6 +1,7 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
@ -9,6 +10,8 @@ import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import UserActions from '../actions/user_actions';
import UserStore from '../stores/user_store';
import CoaActions from '../actions/coa_actions';
import CoaStore from '../stores/coa_store';
import MediaPlayer from './ascribe_media/media_player';
@ -24,12 +27,16 @@ import RequestActionForm from './ascribe_forms/form_request_action';
import EditionActions from '../actions/edition_actions';
import AclButtonList from './ascribe_buttons/acl_button_list';
import fineUploader from 'fineUploader';
import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import requests from '../utils/requests';
import apiUrls from '../constants/api_urls';
import AppConstants from '../constants/application_constants';
let Link = Router.Link;
/**
* This is the component that implements display-specific functionality
*/
@ -93,6 +100,7 @@ let Edition = React.createClass({
<Col md={6} className="ascribe-edition-details">
<EditionHeader edition={this.props.edition}/>
<EditionSummary
currentUser={this.state.currentUser}
edition={this.props.edition} />
<CollapsibleParagraph
@ -109,13 +117,20 @@ let Edition = React.createClass({
</CollapsibleParagraph>
<CollapsibleParagraph
title="Further Details (all editions)"
title="Further Details"
show={this.props.edition.acl.indexOf('edit') > -1 || Object.keys(this.props.edition.extra_data).length > 0}>
<EditionFurtherDetails
handleSuccess={this.props.loadEdition}
edition={this.props.edition}/>
</CollapsibleParagraph>
<CollapsibleParagraph
title="Certificate of Authenticity"
show={this.props.edition.acl.indexOf('coa') > -1}>
<CoaDetails
edition={this.props.edition}/>
</CollapsibleParagraph>
<CollapsibleParagraph
title="Provenance/Ownership History"
show={this.props.edition.ownership_history && this.props.edition.ownership_history.length > 0}>
@ -191,6 +206,9 @@ let EditionSummary = React.createClass({
edition: React.PropTypes.object
},
getTransferWithdrawData(){
return {'bitcoin_id': this.props.edition.bitcoin_id};
},
handleSuccess(){
EditionActions.fetchOne(this.props.edition.id);
},
@ -202,7 +220,24 @@ let EditionSummary = React.createClass({
render() {
let status = null;
if (this.props.edition.status.length > 0){
status = <EditionDetailProperty label="STATUS" value={ this.props.edition.status.join().replace(/_/, ' ') } />;
let statusStr = this.props.edition.status.join().replace(/_/, ' ');
status = <EditionDetailProperty label="STATUS" value={ statusStr }/>;
if (this.props.edition.pending_new_owner && this.props.edition.acl.indexOf('withdraw_transfer') > -1){
status = (
<Form
url={apiUrls.ownership_transfers_withdraw}
getFormData={this.getTransferWithdrawData}
handleSuccess={this.showNotification}>
<EditionDetailProperty label="STATUS" value={ statusStr }>
<button
type="submit"
className="pull-right btn btn-default btn-sm">
WITHDRAW
</button>
</EditionDetailProperty>
</Form>
);
}
}
let actions = null;
if (this.props.edition.request_action && this.props.edition.request_action.length > 0){
@ -262,6 +297,18 @@ let EditionDetailProperty = React.createClass({
},
render() {
let value = this.props.value;
if (this.props.children){
value = (
<div className="row-same-height">
<div className="col-xs-6 col-xs-height col-bottom no-padding">
{ this.props.value }
</div>
<div className="col-xs-6 col-xs-height">
{ this.props.children }
</div>
</div>);
}
return (
<div className="row ascribe-detail-property">
<div className="row-same-height">
@ -269,7 +316,7 @@ let EditionDetailProperty = React.createClass({
<div>{ this.props.label + this.props.separator}</div>
</div>
<div className={this.props.valueClassName + ' col-xs-height col-bottom'}>
<div>{ this.props.value }</div>
{value}
</div>
</div>
</div>
@ -284,19 +331,20 @@ let EditionDetailHistoryIterator = React.createClass({
render() {
return (
<div>
<Form>
{this.props.history.map((historicalEvent, i) => {
return (
<EditionDetailProperty
key={i}
label={historicalEvent[0]}
value={historicalEvent[1]}
labelClassName="col-xs-4 col-sm-4 col-md-4 col-lg-4"
valueClassName="col-xs-8 col-sm-8 col-md-8 col-lg-8"
separator="" />
<Property
name={i}
key={i}
label={ historicalEvent[0] }
editable={false}>
<pre className="ascribe-pre">{ historicalEvent[1] }</pre>
</Property>
);
})}
</div>
<hr />
</Form>
);
}
});
@ -415,10 +463,92 @@ let EditionFurtherDetails = React.createClass({
handleSuccess={this.showNotification}
editable={editable}
edition={this.props.edition} />
<FileUploader
edition={this.props.edition}
ref='uploader'/>
</Col>
</Row>
);
}
});
let FileUploader = React.createClass( {
handleChange(){
this.setState({other_data_key: this.refs.fineuploader.state.filesToUpload[0].key});
},
render() {
return (
<ReactS3FineUploader
ref='fineuploader'
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'otherdata',
bitcoinId: this.props.edition.bitcoin_id
}}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
handleChange={this.handleChange}
validation={{
itemLimit: 100000,
sizeLimit: '10000000'
}}/>
);
}
});
let CoaDetails = React.createClass({
propTypes: {
edition: React.PropTypes.object
},
getInitialState() {
return CoaStore.getState();
},
componentDidMount() {
CoaStore.listen(this.onChange);
if (this.props.edition.coa) {
CoaActions.fetchOne(this.props.edition.coa);
}
else{
console.log('create coa');
CoaActions.create(this.props.edition);
}
},
componentWillUnmount() {
CoaStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
if (this.state.coa.url_safe) {
return (
<div>
<p className="text-center">
<Button bsSize="xsmall" href={this.state.coa.url_safe} target="_blank">
Download <Glyphicon glyph="cloud-download"/>
</Button>
<Link to="coa_verify">
<Button bsSize="xsmall">
Verify <Glyphicon glyph="check"/>
</Button>
</Link>
</p>
</div>
);
}
return (
<div className="text-center">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>);
}
});
export default Edition;

View File

@ -12,6 +12,7 @@ import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
import apiUrls from '../constants/api_urls';
import AppConstants from '../constants/application_constants';
let LoginContainer = React.createClass({
@ -65,7 +66,7 @@ let LoginForm = React.createClass({
Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future.
Until then, we redirect the HARD way, but reloading the whole page using window.location
*/
window.location = '/collection';
window.location = AppConstants.baseUrl + 'collection';
},
render() {

View File

@ -2,8 +2,8 @@
import React from 'react';
import { getCookie } from '../utils/fetch_api_utils';
import AppConstants from '../constants/application_constants';
import fineUploader from 'fineUploader';
import Router from 'react-router';
@ -19,6 +19,7 @@ import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
import DatePicker from 'react-datepicker/dist/react-datepicker';
let RegisterPiece = React.createClass( {
mixins: [Router.Navigation],
@ -135,61 +136,10 @@ let FileUploader = React.createClass({
url: apiUrls.blob_digitalworks
}}
handleChange={this.props.handleChange}
autoUpload={true}
debug={false}
objectProperties={{
acl: 'public-read',
bucket: 'ascribe0'
}}
request={{
endpoint: 'https://ascribe0.s3.amazonaws.com',
accessKey: 'AKIAIVCZJ33WSCBQ3QDA'
}}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/'
}}
uploadSuccess={{
params: {
isBrowserPreviewCapable: fineUploader.supportedFeatures.imagePreviews
}
}}
cors={{
expected: true
}}
chunking={{
enabled: true
}}
resume={{
enabled: true
}}
retry={{
enableAuto: false
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': 'asdasd'
}
}}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
session={{
endpoint: null
}}
messages={{
unsupportedBrowser: '<h3>Upload is not functional in IE7 as IE7 has no support for CORS!</h3>'
}}
formatFileName={(name) => {// fix maybe
if (name !== undefined && name.length > 26) {
name = name.slice(0, 15) + '...' + name.slice(-15);
}
return name;
}}
multiple={true}/>
}}/>
);
}
});

View File

@ -137,7 +137,7 @@ let SignupForm = React.createClass({
name='promo_code'
label="Promocode">
<input
type="password"
type="text"
placeholder="Enter a promocode here (Optional)"/>
</Property>
<hr />

View File

@ -6,6 +6,9 @@ let apiUrls = {
'applications': AppConstants.apiEndpoint + 'applications/',
'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/',
'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/',
'coa': AppConstants.apiEndpoint + 'coa/${id}/',
'coa_create': AppConstants.apiEndpoint + 'coa/',
'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/',
@ -20,6 +23,7 @@ let apiUrls = {
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/',
'ownership_shares': AppConstants.apiEndpoint + 'ownership/shares/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',
'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/',
'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/',

View File

@ -0,0 +1,19 @@
'use strict';
import requests from '../utils/requests';
let CoaFetcher = {
/**
* Fetch one user from the API.
* If no arg is supplied, load the current user
*/
fetchOne(id) {
return requests.get('coa', {'id': id});
},
create(bitcoinId) {
console.log(bitcoinId);
return requests.post('coa_create', {body: {'bitcoin_id': bitcoinId}});
}
};
export default CoaFetcher;

View File

@ -12,6 +12,8 @@ import SignupContainer from './components/signup_container';
import PasswordResetContainer from './components/password_reset_container';
import SettingsContainer from './components/settings_container';
import CoaVerifyContainer from './components/coa_verify_container';
import AppConstants from './constants/application_constants';
import RegisterPiece from './components/register_piece';
@ -28,6 +30,7 @@ let routes = (
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />

18
js/stores/coa_store.js Normal file
View File

@ -0,0 +1,18 @@
'use strict';
import alt from '../alt';
import CoaActions from '../actions/coa_actions';
class CoaStore {
constructor() {
this.coa = {};
this.bindActions(CoaActions);
}
onUpdateCoa(coa) {
this.coa = coa;
}
}
export default alt.createStore(CoaStore, 'CoaStore');

View File

@ -35,7 +35,6 @@
"devDependencies": {
"babel-eslint": "^3.1.11",
"babel-jest": "^5.2.0",
"browserify-shim": "^3.8.9",
"jest-cli": "^0.4.0"
},
"dependencies": {
@ -44,6 +43,7 @@
"bootstrap-sass": "^3.3.4",
"browser-sync": "^2.7.5",
"browserify": "^9.0.8",
"browserify-shim": "^3.8.9",
"classnames": "^1.2.2",
"compression": "^1.4.4",
"envify": "^3.4.0",

View File

@ -28,4 +28,20 @@
width:100%;
margin-top: 1em;
}
.coa-file-wrapper{
display: table;
height: 200px;
overflow: hidden;
margin: 0 auto;
width: 100%;
padding: 1em;
}
.coa-file {
display: table-cell;
vertical-align: middle;
border: 1px solid #CCC;
background-color: #F8F8F8;
}

View File

@ -5,7 +5,8 @@
}
.ascribe-textarea-editable:hover {
border: 1px solid #AAA;
//border: 1px solid #AAA;;
border: none;
}
.ascribe-pre{

View File

@ -32,6 +32,12 @@ body {
display: none;
}
.no-margin{
margin: 0;
}
.no-padding{
padding: 0;
}
.navbar-default {
border: none;
border-left:0;