mirror of
https://github.com/ascribe/onion.git
synced 2024-11-15 01:25:17 +01:00
Merge branch 'piece-detail-setup'
Conflicts: js/components/ascribe_table/table.js js/components/ascribe_table/table_item_subtable.js
This commit is contained in:
commit
d4e55f649d
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@ node_modules
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
||||||
build/app.js
|
build/app.js
|
||||||
|
|
||||||
|
.DS_Store
|
@ -18,9 +18,7 @@ Install some nice extensions for Chrom(e|ium):
|
|||||||
git clone git@bitbucket.org:ascribe/onion.git
|
git clone git@bitbucket.org:ascribe/onion.git
|
||||||
cd onion
|
cd onion
|
||||||
npm install
|
npm install
|
||||||
npm run watch
|
gulp serve
|
||||||
|
|
||||||
python -mSimpleHTTPServer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'ascribe';
|
font-family: 'ascribe';
|
||||||
src:url('fonts/ascribe.eot?-6bb2dq');
|
src:url('ascribe.eot?-6bb2dq');
|
||||||
src:url('fonts/ascribe.eot?#iefix-6bb2dq') format('embedded-opentype'),
|
src:url('ascribe.eot?#iefix-6bb2dq') format('embedded-opentype'),
|
||||||
url('fonts/ascribe.woff?-6bb2dq') format('woff'),
|
url('ascribe.woff?-6bb2dq') format('woff'),
|
||||||
url('fonts/ascribe.ttf?-6bb2dq') format('truetype'),
|
url('ascribe.ttf?-6bb2dq') format('truetype'),
|
||||||
url('fonts/ascribe.svg?-6bb2dq#ascribe') format('svg');
|
url('ascribe.svg?-6bb2dq#ascribe') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@ -187,3 +187,7 @@
|
|||||||
content: "\eae9";
|
content: "\eae9";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-glyph-ascribe{
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 4px 12px 0 10px
|
||||||
|
}
|
173
css/main.css
173
css/main.css
@ -12,15 +12,17 @@
|
|||||||
.ascribe-table-header-row {
|
.ascribe-table-header-row {
|
||||||
border-bottom: 2px solid rgba(2, 182, 163, 0.5);
|
border-bottom: 2px solid rgba(2, 182, 163, 0.5);
|
||||||
border-top: 2px solid rgba(2, 182, 163, 0.5);
|
border-top: 2px solid rgba(2, 182, 163, 0.5);
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-table-header-column {
|
.ascribe-table-header-column {
|
||||||
display: table;
|
display: table;
|
||||||
height:4em;
|
height:3em;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-table-header-column > span {
|
.ascribe-table-header-column > span {
|
||||||
display:table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: 'Source Sans Pro';
|
font-family: 'Source Sans Pro';
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -31,10 +33,10 @@
|
|||||||
.ascribe-table-header-column > span > .glyphicon {
|
.ascribe-table-header-column > span > .glyphicon {
|
||||||
font-size: .5em;
|
font-size: .5em;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
.ascribe-table-item:nth-child(even) {
|
.ascribe-table-item:nth-child(even) {
|
||||||
background-color: #F5F5F5;
|
background-color: #F5F5F5;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/*.ascribe-table-item:hover {
|
/*.ascribe-table-item:hover {
|
||||||
background-color: #EEEEEE;
|
background-color: #EEEEEE;
|
||||||
@ -44,15 +46,168 @@
|
|||||||
display: table;
|
display: table;
|
||||||
font-family: 'Source Sans Pro';
|
font-family: 'Source Sans Pro';
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
height:4em;
|
height:3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-table-item-column > * {
|
.ascribe-table-item-column > * {
|
||||||
display:table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-ascribe, .btn-ascribe:hover, .btn-ascribe:active, .btn-ascribe:focus {
|
.ascribe-table-item-selected {
|
||||||
background-color: rgba(2, 182, 163, 0.5);
|
background-color: rgba(2, 182, 163, 0.5);
|
||||||
border-color: rgba(2, 182, 163, 0.5);
|
}
|
||||||
|
|
||||||
|
.ascribe-table-item-selectable {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piece-list-toolbar {
|
||||||
|
height:3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-margin {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe, .btn-ascribe-inv {
|
||||||
|
border: 1px solid #444;
|
||||||
|
line-height: 2em;
|
||||||
|
margin-right: 1px;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
font-family: sans-serif !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe, .btn-ascribe-inv:active, .btn-ascribe-inv:hover {
|
||||||
|
color: #222 !important;
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe:active, .btn-ascribe:hover, .btn-ascribe-inv {
|
||||||
|
color: #FFF !important;
|
||||||
|
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 {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe-green, .btn-ascribe-green-inv {
|
||||||
|
border: 1px solid #48DACB;
|
||||||
|
line-height: 2em;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
font-family: sans-serif !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe-green, .btn-ascribe-green-inv:active, .btn-ascribe-green-inv:hover {
|
||||||
|
background-color: #FFF;
|
||||||
|
border: 1px solid rgba(2, 182, 163, 0.5);
|
||||||
|
color: rgba(2, 182, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ascribe-green:active, .btn-ascribe-green:hover, .btn-ascribe-green-inv {
|
||||||
|
border: 1px solid rgba(2, 182, 163, 0.5);
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(2, 182, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ascribe-detail-header {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ascribe-detail-title {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: -0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ascribe-detail-property {
|
||||||
|
padding-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
.ascribe-detail-property > .row-same-height > .col-xs-2 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-text-ascribe {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea-ascribe-message {
|
||||||
|
height: 13em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* columns of same height styles */
|
||||||
|
/* http://www.minimit.com/articles/solutions-tutorials/bootstrap-3-responsive-columns-of-same-height */
|
||||||
|
.row-full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-full-height {
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-same-height {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
/* fix overflow */
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-xs-height {
|
||||||
|
display: table-cell;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.col-sm-height {
|
||||||
|
display: table-cell;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.col-md-height {
|
||||||
|
display: table-cell;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.col-lg-height {
|
||||||
|
display: table-cell;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vertical alignment styles */
|
||||||
|
|
||||||
|
.col-top {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-bottom {
|
||||||
|
vertical-align: bottom;
|
||||||
}
|
}
|
67
gulpfile.js
Normal file
67
gulpfile.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
var gulp = require('gulp');
|
||||||
|
var gulpif = require('gulp-if');
|
||||||
|
var sourcemaps = require('gulp-sourcemaps');
|
||||||
|
var util = require('gulp-util');
|
||||||
|
var source = require('vinyl-source-stream');
|
||||||
|
var buffer = require('vinyl-buffer');
|
||||||
|
var watchify = require('watchify');
|
||||||
|
var browserify = require('browserify');
|
||||||
|
var browserSync = require('browser-sync');
|
||||||
|
var babelify = require('babelify');
|
||||||
|
var notify = require('gulp-notify');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
gulp.task('build', function() {
|
||||||
|
bundle(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('serve', ['browser-sync'], function() {
|
||||||
|
bundle(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('browser-sync', function() {
|
||||||
|
browserSync({
|
||||||
|
server: {
|
||||||
|
baseDir: "."
|
||||||
|
},
|
||||||
|
port: process.env.PORT || 3000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function bundle(watch) {
|
||||||
|
var bro;
|
||||||
|
|
||||||
|
if (watch) {
|
||||||
|
bro = watchify(browserify('./js/app.js',
|
||||||
|
// Assigning debug to have sourcemaps
|
||||||
|
_.assign(watchify.args, {
|
||||||
|
debug: true
|
||||||
|
})));
|
||||||
|
bro.on('update', function() {
|
||||||
|
rebundle(bro, true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bro = browserify('./js/app.js', {
|
||||||
|
debug: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bro.transform(babelify.configure({
|
||||||
|
compact: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
function rebundle(bundler, watch) {
|
||||||
|
return bundler.bundle()
|
||||||
|
.on('error', notify.onError('Error: <%= error.message %>'))
|
||||||
|
.pipe(source('app.js'))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(sourcemaps.init({
|
||||||
|
loadMaps: true
|
||||||
|
})) // loads map from browserify file
|
||||||
|
.pipe(sourcemaps.write()) // writes .map file
|
||||||
|
.pipe(gulp.dest('./build'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rebundle(bro);
|
||||||
|
}
|
@ -5,13 +5,15 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>ascribe</title>
|
<title>ascribe</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"></link>
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="css/main.css"></link>
|
<link rel="stylesheet" href="css/main.css">
|
||||||
<link rel="stylesheet" href="css/ascribe-fonts/ascribe-fonts.css"></link>
|
<link rel="stylesheet" href="css/ascribe-fonts/ascribe-fonts.css">
|
||||||
|
<link rel="stylesheet" href="css/ascribe-fonts/style.css">
|
||||||
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main" class="container"></div>
|
<div id="main" class="container"></div>
|
||||||
|
<div id="modal" class="container"></div>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
||||||
|
23
js/actions/edition_actions.js
Normal file
23
js/actions/edition_actions.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import alt from '../alt';
|
||||||
|
import EditionFetcher from '../fetchers/edition_fetcher';
|
||||||
|
|
||||||
|
|
||||||
|
class EditionActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'updateEdition'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchOne(editionId) {
|
||||||
|
EditionFetcher.fetchOne(editionId)
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updateEdition(res.edition);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(EditionActions);
|
@ -5,7 +5,8 @@ import EditionListFetcher from '../fetchers/edition_list_fetcher.js';
|
|||||||
class EditionListActions {
|
class EditionListActions {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generateActions(
|
this.generateActions(
|
||||||
'updateEditionList'
|
'updateEditionList',
|
||||||
|
'selectEdition'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ class EditionListActions {
|
|||||||
.fetch(pieceId)
|
.fetch(pieceId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.actions.updateEditionList({
|
this.actions.updateEditionList({
|
||||||
'editionList': res.editions,
|
'editionListOfPiece': res.editions,
|
||||||
pieceId
|
pieceId
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -22,6 +23,6 @@ class EditionListActions {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default alt.createActions(EditionListActions);
|
export default alt.createActions(EditionListActions);
|
23
js/actions/piece_actions.js
Normal file
23
js/actions/piece_actions.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import alt from '../alt';
|
||||||
|
import PieceFetcher from '../fetchers/piece_fetcher';
|
||||||
|
|
||||||
|
|
||||||
|
class PieceActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'updatePiece'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchOne(pieceId) {
|
||||||
|
PieceFetcher.fetchOne(pieceId)
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updatePiece(res.piece);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(PieceActions);
|
32
js/components/acl_button.js
Normal file
32
js/components/acl_button.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AppConstants from '../constants/application_constants';
|
||||||
|
|
||||||
|
let AclButton = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
|
||||||
|
availableAcls: React.PropTypes.array.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1;
|
||||||
|
let styles = {};
|
||||||
|
|
||||||
|
if(shouldDisplay) {
|
||||||
|
styles.display = 'inline-block';
|
||||||
|
} else {
|
||||||
|
styles.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
style={styles}
|
||||||
|
type="button"
|
||||||
|
className="btn btn-default btn-sm">
|
||||||
|
{this.props.action.toUpperCase()}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AclButton;
|
31
js/components/ascribe_forms/alert.js
Normal file
31
js/components/ascribe_forms/alert.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Alert from 'react-bootstrap/lib/Alert';
|
||||||
|
|
||||||
|
let AlertDismissable = React.createClass({
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
alertVisible: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
this.setState({alertVisible: true});
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.setState({alertVisible: false});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
if (this.state.alertVisible) {
|
||||||
|
let key = this.props.error;
|
||||||
|
return (
|
||||||
|
<Alert bsStyle='danger' onDismiss={this.hide}>
|
||||||
|
{this.props.error}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AlertDismissable;
|
21
js/components/ascribe_forms/button_submit_close.js
Normal file
21
js/components/ascribe_forms/button_submit_close.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
let ButtonSubmitOrClose = React.createClass({
|
||||||
|
render() {
|
||||||
|
if (this.props.submitted){
|
||||||
|
return (
|
||||||
|
<div className="modal-footer">
|
||||||
|
Loading
|
||||||
|
</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}>CLOSE</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ButtonSubmitOrClose;
|
53
js/components/ascribe_forms/form_share_email.js
Normal file
53
js/components/ascribe_forms/form_share_email.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import FormMixin from '../../mixins/form_mixin';
|
||||||
|
import InputText from './input_text';
|
||||||
|
import InputTextArea from './input_textarea';
|
||||||
|
import ButtonSubmitOrClose from './button_submit_close';
|
||||||
|
|
||||||
|
let ShareForm = React.createClass({
|
||||||
|
mixins: [FormMixin],
|
||||||
|
|
||||||
|
url() {
|
||||||
|
return ApiUrls.ownership_shares_mail
|
||||||
|
},
|
||||||
|
getFormData() {
|
||||||
|
return {
|
||||||
|
bitcoin_id: this.props.edition.bitcoin_id,
|
||||||
|
share_emails: this.refs.share_emails.state.value,
|
||||||
|
share_message: this.refs.share_message.state.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderForm() {
|
||||||
|
let message = "Hi,\n" +
|
||||||
|
"\n" +
|
||||||
|
"I am sharing \"" + this.props.edition.title + "\" with you.\n" +
|
||||||
|
"\n" +
|
||||||
|
"Truly yours,\n" +
|
||||||
|
this.props.currentUser.username;
|
||||||
|
return (
|
||||||
|
<form id="share_modal_content" role="form" key="share_modal_content" onSubmit={this.submit}>
|
||||||
|
<InputText
|
||||||
|
ref="share_emails"
|
||||||
|
placeHolder="Comma separated emails"
|
||||||
|
required="required"
|
||||||
|
type="text"
|
||||||
|
submitted={this.state.submitted}/>
|
||||||
|
<InputTextArea
|
||||||
|
ref="share_message"
|
||||||
|
defaultValue={message}
|
||||||
|
required=""
|
||||||
|
/>
|
||||||
|
<ButtonSubmitOrClose
|
||||||
|
text="SHARE"
|
||||||
|
onClose={this.props.onRequestHide}
|
||||||
|
submitted={this.state.submitted} />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ShareForm;
|
68
js/components/ascribe_forms/form_transfer.js
Normal file
68
js/components/ascribe_forms/form_transfer.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import FormMixin from '../../mixins/form_mixin';
|
||||||
|
import InputText from './input_text';
|
||||||
|
import InputTextArea from './input_textarea';
|
||||||
|
import ButtonSubmitOrClose from './button_submit_close';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let TransferForm = React.createClass({
|
||||||
|
mixins: [FormMixin],
|
||||||
|
|
||||||
|
url() {
|
||||||
|
return ApiUrls.ownership_transfers
|
||||||
|
},
|
||||||
|
getFormData() {
|
||||||
|
return {
|
||||||
|
bitcoin_id: this.props.edition.bitcoin_id,
|
||||||
|
transferee: this.refs.transferee.state.value,
|
||||||
|
transfer_message: this.refs.transfer_message.state.value,
|
||||||
|
password: this.refs.password.state.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderForm() {
|
||||||
|
let message = "Hi,\n" +
|
||||||
|
"\n" +
|
||||||
|
"I transfer ownership of \"" + this.props.edition.title + "\" to you.\n" +
|
||||||
|
"\n" +
|
||||||
|
"Truly yours,\n" +
|
||||||
|
this.props.currentUser.username;
|
||||||
|
return (
|
||||||
|
<form id="transfer_modal_content" role="form" onSubmit={this.submit}>
|
||||||
|
<input className="invisible" type="email" name="fake_transferee"/>
|
||||||
|
<input className="invisible" type="password" name="fake_password"/>
|
||||||
|
<InputText
|
||||||
|
ref="transferee"
|
||||||
|
placeHolder="Transferee email"
|
||||||
|
required="required"
|
||||||
|
type="email"
|
||||||
|
submitted={this.state.submitted}/>
|
||||||
|
<InputTextArea
|
||||||
|
ref="transfer_message"
|
||||||
|
defaultValue={message}
|
||||||
|
required=""
|
||||||
|
/>
|
||||||
|
<InputText
|
||||||
|
ref="password"
|
||||||
|
placeHolder="Password"
|
||||||
|
required="required"
|
||||||
|
type="password"
|
||||||
|
submitted={this.state.submitted}/>
|
||||||
|
<div>
|
||||||
|
Make sure that display instructions and technology details are correct.
|
||||||
|
They cannot be edited after the transfer.
|
||||||
|
</div>
|
||||||
|
<ButtonSubmitOrClose
|
||||||
|
text="TRANSFER"
|
||||||
|
onClose={this.props.onRequestHide}
|
||||||
|
submitted={this.state.submitted} />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TransferForm;
|
35
js/components/ascribe_forms/input_text.js
Normal file
35
js/components/ascribe_forms/input_text.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AlertMixin from '../../mixins/alert_mixin'
|
||||||
|
|
||||||
|
let InputText = React.createClass({
|
||||||
|
|
||||||
|
mixins : [AlertMixin],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {value: null,
|
||||||
|
alerts: null, // needed in AlertMixin
|
||||||
|
retry: 0 // needed in AlertMixin for generating unique alerts
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({value: event.target.value});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
let className = "form-control input-text-ascribe";
|
||||||
|
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||||
|
return (
|
||||||
|
<div className="form-group">
|
||||||
|
{alerts}
|
||||||
|
<input className={className}
|
||||||
|
placeholder={this.props.placeHolder}
|
||||||
|
required={this.props.required}
|
||||||
|
type={this.props.type}
|
||||||
|
onChange={this.handleChange}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputText;
|
35
js/components/ascribe_forms/input_textarea.js
Normal file
35
js/components/ascribe_forms/input_textarea.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AlertMixin from '../../mixins/alert_mixin'
|
||||||
|
|
||||||
|
let InputTextArea = React.createClass({
|
||||||
|
|
||||||
|
mixins : [AlertMixin],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {value: this.props.defaultValue,
|
||||||
|
alerts: null, // needed in AlertMixin
|
||||||
|
retry: 0 // needed in AlertMixin for generating unique alerts
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({value: event.target.value});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
let className = "form-control input-text-ascribe textarea-ascribe-message";
|
||||||
|
|
||||||
|
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||||
|
return (
|
||||||
|
<div className="form-group">
|
||||||
|
{alerts}
|
||||||
|
<textarea className={className}
|
||||||
|
defaultValue={this.props.defaultValue}
|
||||||
|
required={this.props.required}
|
||||||
|
onChange={this.handleChange}></textarea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputTextArea;
|
25
js/components/ascribe_media/image_viewer.js
Normal file
25
js/components/ascribe_media/image_viewer.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the component that implements display-specific functionality
|
||||||
|
*/
|
||||||
|
let ImageViewer = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
thumbnail: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let thumbnail = <img className="img-responsive" src={this.props.thumbnail}/>;
|
||||||
|
let aligner = <span className="vcenter"></span>;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{aligner}
|
||||||
|
{thumbnail}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ImageViewer;
|
46
js/components/ascribe_modal/modal_share.js
Normal file
46
js/components/ascribe_modal/modal_share.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'react-bootstrap/lib/Modal';
|
||||||
|
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||||
|
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||||
|
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import ShareForm from '../ascribe_forms/form_share_email'
|
||||||
|
|
||||||
|
let ShareModalButton = React.createClass({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<OverlayTrigger delay={500} placement="left" overlay={<Tooltip>Share the artwork</Tooltip>}>
|
||||||
|
<ModalTrigger modal={<ShareModal edition={this.props.edition}
|
||||||
|
currentUser={this.props.currentUser}/>}>
|
||||||
|
<div className="btn btn-ascribe-inv btn-glyph-ascribe">
|
||||||
|
<span className="glyph-ascribe-share2"></span>
|
||||||
|
</div>
|
||||||
|
</ModalTrigger>
|
||||||
|
</OverlayTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ShareModal = React.createClass({
|
||||||
|
onRequestHide(e){
|
||||||
|
if (e)
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onRequestHide();
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal {...this.props} title="Share artwork">
|
||||||
|
<div className="modal-body">
|
||||||
|
<ShareForm edition={this.props.edition}
|
||||||
|
currentUser={this.props.currentUser}
|
||||||
|
onRequestHide={this.onRequestHide}/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default ShareModalButton;
|
45
js/components/ascribe_modal/modal_transfer.js
Normal file
45
js/components/ascribe_modal/modal_transfer.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'react-bootstrap/lib/Modal';
|
||||||
|
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||||
|
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||||
|
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||||
|
|
||||||
|
import TransferForm from '../ascribe_forms/form_transfer'
|
||||||
|
|
||||||
|
|
||||||
|
let TransferModalButton = React.createClass({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<OverlayTrigger delay={500} placement="left"
|
||||||
|
overlay={<Tooltip>Transfer the ownership of the artwork</Tooltip>}>
|
||||||
|
<ModalTrigger modal={<TransferModal edition={this.props.edition}
|
||||||
|
currentUser={this.props.currentUser}/>}>
|
||||||
|
<div className="btn btn-ascribe-inv">
|
||||||
|
TRANSFER
|
||||||
|
</div>
|
||||||
|
</ModalTrigger>
|
||||||
|
</OverlayTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let TransferModal = React.createClass({
|
||||||
|
onRequestHide(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onRequestHide();
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal {...this.props} title="Transfer artwork">
|
||||||
|
<div className="modal-body">
|
||||||
|
<TransferForm edition={this.props.edition}
|
||||||
|
currentUser={this.props.currentUser}
|
||||||
|
onRequestHide={this.onRequestHide}/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default TransferModalButton;
|
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import EditionListStore from '../../stores/edition_list_store';
|
||||||
|
|
||||||
|
import AclButton from '../acl_button';
|
||||||
|
|
||||||
|
let PieceListToolbar = React.createClass({
|
||||||
|
getInitialState() {
|
||||||
|
return EditionListStore.getState();
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
EditionListStore.listen(this.onChange)
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUnmount() {
|
||||||
|
EditionListStore.unlisten(this.onChange)
|
||||||
|
},
|
||||||
|
|
||||||
|
filterForSelected(edition) {
|
||||||
|
return edition.selected;
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchSelectedEditionList() {
|
||||||
|
let selectedEditionList = [];
|
||||||
|
|
||||||
|
Object
|
||||||
|
.keys(this.state.editionList)
|
||||||
|
.forEach((key) => {
|
||||||
|
let filteredEditionsForPiece = this.state.editionList[key].filter(this.filterForSelected);
|
||||||
|
selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece);
|
||||||
|
});
|
||||||
|
|
||||||
|
return selectedEditionList;
|
||||||
|
},
|
||||||
|
|
||||||
|
intersectAcls(a, b) {
|
||||||
|
return a.filter((val) => b.indexOf(val) > -1);
|
||||||
|
},
|
||||||
|
|
||||||
|
getAvailableAcls() {
|
||||||
|
let availableAcls = [];
|
||||||
|
let selectedEditionList = this.fetchSelectedEditionList();
|
||||||
|
|
||||||
|
// If no edition has been selected, availableActions is empty
|
||||||
|
// If only one edition has been selected, their actions are available
|
||||||
|
// If more than one editions have been selected, their acl properties are intersected
|
||||||
|
if(selectedEditionList.length >= 1) {
|
||||||
|
availableAcls = selectedEditionList[0].acl;
|
||||||
|
}
|
||||||
|
if(selectedEditionList.length >= 2) {
|
||||||
|
for(let i = 1; i < selectedEditionList.length; i++) {
|
||||||
|
availableAcls = this.intersectAcls(availableAcls, selectedEditionList[i].acl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableAcls;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let availableAcls = this.getAvailableAcls();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row no-margin">
|
||||||
|
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 piece-list-toolbar">
|
||||||
|
<div className="pull-right">
|
||||||
|
<AclButton availableAcls={availableAcls} action="transfer" />
|
||||||
|
<AclButton availableAcls={availableAcls} action="consign" />
|
||||||
|
<AclButton availableAcls={availableAcls} action="share" />
|
||||||
|
<AclButton availableAcls={availableAcls} action="loan" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PieceListToolbar;
|
@ -27,12 +27,13 @@ let Table = React.createClass({
|
|||||||
if(this.props.itemList && this.props.itemList.length > 0) {
|
if(this.props.itemList && this.props.itemList.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="ascribe-table">
|
<div className="ascribe-table">
|
||||||
<TableHeader columnList={this.props.columnList}
|
<TableHeader
|
||||||
itemList={this.props.itemList}
|
columnList={this.props.columnList}
|
||||||
fetchList={this.props.fetchList}
|
itemList={this.props.itemList}
|
||||||
changeOrder={this.props.changeOrder}
|
fetchList={this.props.fetchList}
|
||||||
orderAsc={this.props.orderAsc}
|
changeOrder={this.props.changeOrder}
|
||||||
orderBy={this.props.orderBy} />
|
orderAsc={this.props.orderAsc}
|
||||||
|
orderBy={this.props.orderBy} />
|
||||||
{this.renderChildren()}
|
{this.renderChildren()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -12,9 +12,9 @@ let TableHeader = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
||||||
itemList: React.PropTypes.array.isRequired,
|
itemList: React.PropTypes.array.isRequired,
|
||||||
changeOrder: React.PropTypes.func.isRequired,
|
changeOrder: React.PropTypes.func,
|
||||||
orderAsc: React.PropTypes.bool.isRequired,
|
orderAsc: React.PropTypes.bool,
|
||||||
orderBy: React.PropTypes.string.isRequired
|
orderBy: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -23,7 +23,7 @@ let TableHeader = React.createClass({
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
{this.props.columnList.map((val, i) => {
|
{this.props.columnList.map((val, i) => {
|
||||||
|
|
||||||
let columnClasses = this.calcColumnClasses(this.props.columnList, i);
|
let columnClasses = this.calcColumnClasses(this.props.columnList, i, 12);
|
||||||
let columnName = this.props.columnList[i].columnName;
|
let columnName = this.props.columnList[i].columnName;
|
||||||
let canBeOrdered = this.props.columnList[i].canBeOrdered;
|
let canBeOrdered = this.props.columnList[i].canBeOrdered;
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@ let TableHeaderItem = React.createClass({
|
|||||||
columnClasses: React.PropTypes.string.isRequired,
|
columnClasses: React.PropTypes.string.isRequired,
|
||||||
displayName: React.PropTypes.string.isRequired,
|
displayName: React.PropTypes.string.isRequired,
|
||||||
columnName: React.PropTypes.string.isRequired,
|
columnName: React.PropTypes.string.isRequired,
|
||||||
canBeOrdered: React.PropTypes.bool.isRequired,
|
canBeOrdered: React.PropTypes.bool,
|
||||||
changeOrder: React.PropTypes.func.isRequired,
|
changeOrder: React.PropTypes.func,
|
||||||
orderAsc: React.PropTypes.bool.isRequired,
|
orderAsc: React.PropTypes.bool,
|
||||||
orderBy: React.PropTypes.string.isRequired
|
orderBy: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
changeOrder() {
|
changeOrder() {
|
||||||
@ -19,7 +19,7 @@ let TableHeaderItem = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(this.props.canBeOrdered) {
|
if(this.props.canBeOrdered && this.props.changeOrder && this.props.orderAsc != null && this.props.orderBy) {
|
||||||
if(this.props.columnName === this.props.orderBy) {
|
if(this.props.columnName === this.props.orderBy) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,38 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import TableColumnMixin from '../../mixins/table_column_mixin';
|
|
||||||
|
|
||||||
import TableColumnContentModel from '../../models/table_column_content_model';
|
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||||
|
|
||||||
|
import TableItemWrapper from './table_item_wrapper';
|
||||||
|
|
||||||
|
|
||||||
let TableItem = React.createClass({
|
let TableItem = React.createClass({
|
||||||
mixins: [TableColumnMixin],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
||||||
columnContent: React.PropTypes.object
|
columnContent: React.PropTypes.object,
|
||||||
|
onClick: React.PropTypes.func, // See: https://facebook.github.io/react/tips/expose-component-functions.html
|
||||||
|
classNames: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let calcColumnElementContent = () => {
|
|
||||||
return this.props.columnList.map((column, i) => {
|
|
||||||
|
|
||||||
let TypeElement = column.displayType;
|
|
||||||
let columnClass = this.calcColumnClasses(this.props.columnList, i);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={columnClass + ' ascribe-table-item-column'} key={i}>
|
|
||||||
<TypeElement content={this.props.columnContent[column.columnName]} width="50" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item">
|
<div
|
||||||
|
className={this.props.classNames + ' col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item'}
|
||||||
|
onClick={this.props.onClick}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{calcColumnElementContent()}
|
<TableItemWrapper
|
||||||
|
columnList={this.props.columnList}
|
||||||
|
columnContent={this.props.columnContent}
|
||||||
|
columnWidth={12}>
|
||||||
|
</TableItemWrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
18
js/components/ascribe_table/table_item_acl.js
Normal file
18
js/components/ascribe_table/table_item_acl.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
let TableItemAcl = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
content: React.PropTypes.array.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{this.props.content.join('/')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TableItemAcl;
|
38
js/components/ascribe_table/table_item_selectable.js
Normal file
38
js/components/ascribe_table/table_item_selectable.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||||
|
|
||||||
|
import TableItem from './table_item';
|
||||||
|
|
||||||
|
// This component is implemented as recommended here: http://stackoverflow.com/a/25723635/1263876
|
||||||
|
let TableItemSelectable = React.createClass({
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
||||||
|
columnContent: React.PropTypes.object,
|
||||||
|
parentId: React.PropTypes.number,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
selectItem() {
|
||||||
|
this.props.selectItem(this.props.parentId, this.props.columnContent.edition_number);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let tableItemClasses = classNames({
|
||||||
|
'ascribe-table-item-selected': this.props.columnContent.selected
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableItem
|
||||||
|
classNames={tableItemClasses + ' ' + this.props.className}
|
||||||
|
columnList={this.props.columnList}
|
||||||
|
columnContent={this.props.columnContent}
|
||||||
|
onClick={this.selectItem}>
|
||||||
|
</TableItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TableItemSelectable;
|
@ -5,12 +5,12 @@ import TableColumnContentModel from '../../models/table_column_content_model';
|
|||||||
import EditionListStore from '../../stores/edition_list_store';
|
import EditionListStore from '../../stores/edition_list_store';
|
||||||
import EditionListActions from '../../actions/edition_list_actions';
|
import EditionListActions from '../../actions/edition_list_actions';
|
||||||
|
|
||||||
// ToDo: Create Table-specific Utils to not lock it to projects utilities
|
|
||||||
import GeneralUtils from '../../utils/general_utils';
|
|
||||||
|
|
||||||
import Table from './table';
|
import Table from './table';
|
||||||
import TableItem from './table_item';
|
import TableItemWrapper from './table_item_wrapper';
|
||||||
import TableItemText from './table_item_text';
|
import TableItemText from './table_item_text';
|
||||||
|
import TableItemAcl from './table_item_acl';
|
||||||
|
import TableItemSelectable from './table_item_selectable';
|
||||||
import TableItemSubtableButton from './table_item_subtable_button';
|
import TableItemSubtableButton from './table_item_subtable_button';
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ let TableItemSubtable = React.createClass({
|
|||||||
'open': false
|
'open': false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
EditionListActions.fetchEditionList(this.props.columnContent.id);
|
EditionListActions.fetchEditionList(this.props.columnContent.id);
|
||||||
this.setState({
|
this.setState({
|
||||||
'open': true,
|
'open': true,
|
||||||
@ -48,43 +49,21 @@ let TableItemSubtable = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
calcColumnClasses(list, i) {
|
selectItem(parentId, itemId) {
|
||||||
let bootstrapClasses = ['col-xs-', 'col-sm-', 'col-md-', 'col-lg-'];
|
EditionListActions.selectEdition({
|
||||||
|
'pieceId': parentId,
|
||||||
let listOfRowValues = list.map((column) => column.rowWidth );
|
'editionId': itemId
|
||||||
let numOfColumns = GeneralUtils.sumNumList(listOfRowValues);
|
});
|
||||||
|
|
||||||
if(numOfColumns > 10) {
|
|
||||||
throw new Error('Bootstrap has only 12 columns to assign. You defined ' + numOfColumns + '. Change this in the columnMap you\'re passing to the table.')
|
|
||||||
} else {
|
|
||||||
return bootstrapClasses.join( listOfRowValues[i] + ' ') + listOfRowValues[i];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
let calcColumnElementContent = () => {
|
|
||||||
return this.props.columnList.map((column, i) => {
|
|
||||||
|
|
||||||
let TypeElement = column.displayType;
|
|
||||||
let columnClass = this.calcColumnClasses(this.props.columnList, i);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={columnClass + ' ascribe-table-item-column'} key={i}>
|
|
||||||
<TypeElement content={this.props.columnContent[column.columnName]} width="50" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let renderEditionListTable = () => {
|
let renderEditionListTable = () => {
|
||||||
|
|
||||||
let columnList = [
|
let columnList = [
|
||||||
new TableColumnContentModel('edition_number', 'Edition Number', TableItemText, 2, false),
|
new TableColumnContentModel('edition_number', 'Edition Number', TableItemText, 2, false),
|
||||||
new TableColumnContentModel('user_registered', 'User', TableItemText, 4, true),
|
new TableColumnContentModel('user_registered', 'User', TableItemText, 4, true),
|
||||||
new TableColumnContentModel('bitcoin_id', 'Bitcoin Address', TableItemText, 4, true)
|
new TableColumnContentModel('acl', 'Actions', TableItemAcl, 4, true)
|
||||||
];
|
];
|
||||||
|
|
||||||
if(this.state.open && this.state.editionList[this.props.columnContent.id] && this.state.editionList[this.props.columnContent.id].length) {
|
if(this.state.open && this.state.editionList[this.props.columnContent.id] && this.state.editionList[this.props.columnContent.id].length) {
|
||||||
@ -94,9 +73,12 @@ let TableItemSubtable = React.createClass({
|
|||||||
<Table itemList={this.state.editionList[this.props.columnContent.id]} columnList={columnList}>
|
<Table itemList={this.state.editionList[this.props.columnContent.id]} columnList={columnList}>
|
||||||
{this.state.editionList[this.props.columnContent.id].map((edition, i) => {
|
{this.state.editionList[this.props.columnContent.id].map((edition, i) => {
|
||||||
return (
|
return (
|
||||||
<TableItem
|
<TableItemSelectable
|
||||||
|
className="ascribe-table-item-selectable"
|
||||||
|
selectItem={this.selectItem}
|
||||||
|
parentId={this.props.columnContent.id}
|
||||||
key={i}>
|
key={i}>
|
||||||
</TableItem>
|
</TableItemSelectable>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Table>
|
</Table>
|
||||||
@ -109,10 +91,14 @@ let TableItemSubtable = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item">
|
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{calcColumnElementContent()}
|
<TableItemWrapper
|
||||||
<div className="col-xs-2 col-sm-2 col-md-2 col-lg-2 ascribe-table-item-column">
|
columnList={this.props.columnList}
|
||||||
<TableItemSubtableButton content={this.props.columnContent['num_editions'] + ' Editions'}
|
columnContent={this.props.columnContent}
|
||||||
onClick={this.loadEditionList} />
|
columnWidth={12}>
|
||||||
|
</TableItemWrapper>
|
||||||
|
<div className="col-xs-1 col-sm-1 col-md-1 col-lg-1 ascribe-table-item-column">
|
||||||
|
<TableItemSubtableButton content="+" onClick={this.loadEditionList}>
|
||||||
|
</TableItemSubtableButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{renderEditionListTable()}
|
{renderEditionListTable()}
|
||||||
|
@ -10,7 +10,7 @@ let TableItemSubtableButton = React.createClass({
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<button type="button" className="btn btn-ascribe btn-primary btn-sm" onClick={this.props.onClick}>
|
<button type="button" className="btn btn-default btn-sm ascribe-table-expand-button" onClick={this.props.onClick}>
|
||||||
{this.props.content}
|
{this.props.content}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
34
js/components/ascribe_table/table_item_wrapper.js
Normal file
34
js/components/ascribe_table/table_item_wrapper.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||||
|
import TableColumnMixin from '../../mixins/table_column_mixin';
|
||||||
|
|
||||||
|
let TableItemWrapper = React.createClass({
|
||||||
|
mixins: [TableColumnMixin],
|
||||||
|
propTypes: {
|
||||||
|
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
||||||
|
columnContent: React.PropTypes.object,
|
||||||
|
columnWidth: React.PropTypes.number.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.props.columnList.map((column, i) => {
|
||||||
|
|
||||||
|
let TypeElement = column.displayType;
|
||||||
|
let columnClass = this.calcColumnClasses(this.props.columnList, i, this.props.columnWidth);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={columnClass + ' ascribe-table-item-column'} key={i}>
|
||||||
|
<TypeElement content={this.props.columnContent[column.columnName]} width="50" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TableItemWrapper;
|
78
js/components/edition.js
Normal file
78
js/components/edition.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ImageViewer from './ascribe_media/image_viewer';
|
||||||
|
import TransferModalButton from './ascribe_modal/modal_transfer';
|
||||||
|
import ShareModalButton from './ascribe_modal/modal_share';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the component that implements display-specific functionality
|
||||||
|
*/
|
||||||
|
let Edition = React.createClass({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="col-md-7">
|
||||||
|
<ImageViewer thumbnail={this.props.edition.thumbnail}/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-5">
|
||||||
|
<EditionHeader edition={this.props.edition}/>
|
||||||
|
<EditionDetails edition={this.props.edition} currentUser={ this.props.currentUser }/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let EditionHeader = React.createClass({
|
||||||
|
render() {
|
||||||
|
var title_html = <div className="ascribe-detail-title">{this.props.edition.title}</div>;
|
||||||
|
return (
|
||||||
|
<div className="ascribe-detail-header">
|
||||||
|
<EditionDetailProperty label="title" value={title_html} />
|
||||||
|
<EditionDetailProperty label="by" value={this.props.edition.artist_name} />
|
||||||
|
<EditionDetailProperty label="date" value={ this.props.edition.date_created.slice(0,4) } />
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let EditionDetails = React.createClass({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="ascribe-detail-header">
|
||||||
|
<EditionDetailProperty label="edition"
|
||||||
|
value={this.props.edition.edition_number + " of " + this.props.edition.num_editions} />
|
||||||
|
<EditionDetailProperty label="id" value={ this.props.edition.bitcoin_id } />
|
||||||
|
<EditionDetailProperty label="owner" value={ this.props.edition.owner } />
|
||||||
|
<br/>
|
||||||
|
<TransferModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
|
<ShareModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let EditionDetailProperty = React.createClass({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="row ascribe-detail-property">
|
||||||
|
<div className="row-same-height">
|
||||||
|
<div className="col-xs-2 col-xs-height col-bottom">
|
||||||
|
<div>{ this.props.label }:</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-xs-10 col-xs-height col-bottom">
|
||||||
|
<div>{ this.props.value }</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default Edition;
|
||||||
|
|
52
js/components/edition_container.js
Normal file
52
js/components/edition_container.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import EditionActions from '../actions/edition_actions';
|
||||||
|
import EditionStore from '../stores/edition_store';
|
||||||
|
import UserActions from '../actions/user_actions';
|
||||||
|
import UserStore from '../stores/user_store';
|
||||||
|
|
||||||
|
import Edition from './edition';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the component that implements resource/data specific functionality
|
||||||
|
*/
|
||||||
|
let EditionContainer = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {'user': UserStore.getState(),
|
||||||
|
'edition': EditionStore.getState()}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
EditionActions.fetchOne(this.props.params.editionId);
|
||||||
|
EditionStore.listen(this.onChange);
|
||||||
|
UserActions.fetchCurrentUser();
|
||||||
|
UserStore.listen(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUnmount() {
|
||||||
|
EditionStore.unlisten(this.onChange);
|
||||||
|
UserStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
if('title' in this.state.edition) {
|
||||||
|
return (
|
||||||
|
<Edition edition={this.state.edition } currentUser={this.state.currentUser}></Edition>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<p>Loading</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default EditionContainer;
|
@ -1,12 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
|
|
||||||
let Piece = React.createClass({
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<p>this.props.piece.title</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Piece;
|
|
@ -13,7 +13,9 @@ import TableItemSubtableButton from './ascribe_table/table_item_subtable_button'
|
|||||||
|
|
||||||
import TableColumnContentModel from '../models/table_column_content_model';
|
import TableColumnContentModel from '../models/table_column_content_model';
|
||||||
|
|
||||||
import Pagination from './ascribe_pagination/pagination'
|
import Pagination from './ascribe_pagination/pagination';
|
||||||
|
|
||||||
|
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||||
|
|
||||||
|
|
||||||
let PieceList = React.createClass({
|
let PieceList = React.createClass({
|
||||||
@ -60,6 +62,7 @@ let PieceList = React.createClass({
|
|||||||
if(this.state.pieceList && this.state.pieceList.length > 0) {
|
if(this.state.pieceList && this.state.pieceList.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<PieceListToolbar />
|
||||||
<Table
|
<Table
|
||||||
columnList={columnList}
|
columnList={columnList}
|
||||||
changeOrder={this.tableChangeOrder}
|
changeOrder={this.tableChangeOrder}
|
||||||
|
8
js/constants/api_urls.js
Normal file
8
js/constants/api_urls.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import AppConstants from './application_constants';
|
||||||
|
|
||||||
|
let apiUrls = {
|
||||||
|
'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/',
|
||||||
|
'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default apiUrls;
|
@ -1,6 +1,7 @@
|
|||||||
let constants = {
|
let constants = {
|
||||||
'baseUrl': 'http://staging.ascribe.io/api/',
|
'baseUrl': 'http://localhost:8000/api/',
|
||||||
'debugCredentialBase64': 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw' // dimi@mailinator:0000000000
|
'debugCredentialBase64': 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw', // dimi@mailinator:0000000000
|
||||||
|
'aclList': ['edit', 'consign', 'transfer', 'loan', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection']
|
||||||
};
|
};
|
||||||
|
|
||||||
export default constants;
|
export default constants;
|
24
js/fetchers/edition_fetcher.js
Normal file
24
js/fetchers/edition_fetcher.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import AppConstants from '../constants/application_constants';
|
||||||
|
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let EditionFetcher = {
|
||||||
|
/**
|
||||||
|
* Fetch one user from the API.
|
||||||
|
* If no arg is supplied, load the current user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fetchOne(editionId) {
|
||||||
|
return fetch(AppConstants.baseUrl + 'editions/' + editionId + '/', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic ' + AppConstants.debugCredentialBase64
|
||||||
|
}
|
||||||
|
}).then(
|
||||||
|
(res) => res.json()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditionFetcher;
|
22
js/fetchers/piece_fetcher.js
Normal file
22
js/fetchers/piece_fetcher.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import AppConstants from '../constants/application_constants';
|
||||||
|
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let PieceFetcher = {
|
||||||
|
/**
|
||||||
|
* Fetch one user from the API.
|
||||||
|
* If no arg is supplied, load the current user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fetchOne(pieceId) {
|
||||||
|
return fetch(AppConstants.baseUrl + 'pieces/' + pieceId + '/', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic ' + AppConstants.debugCredentialBase64
|
||||||
|
}
|
||||||
|
}).then((res) => res.json());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PieceFetcher;
|
16
js/mixins/alert_mixin.js
Normal file
16
js/mixins/alert_mixin.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AlertDismissable from '../components/ascribe_forms/alert';
|
||||||
|
|
||||||
|
let AlertMixin = {
|
||||||
|
setAlerts(errors){
|
||||||
|
let alerts = errors.map(
|
||||||
|
function(error) {
|
||||||
|
let key = error + this.state.retry;
|
||||||
|
return <AlertDismissable error={error} key={key}/>;
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
this.setState({alerts: alerts, retry: this.state.retry + 1});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlertMixin;
|
67
js/mixins/form_mixin.js
Normal file
67
js/mixins/form_mixin.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AppConstants from '../constants/application_constants'
|
||||||
|
import AlertDismissable from '../components/ascribe_forms/alert'
|
||||||
|
|
||||||
|
export const FormMixin = {
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
submitted: false
|
||||||
|
, status: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({submitted: true});
|
||||||
|
fetch(this.url(), {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic ' + AppConstants.debugCredentialBase64,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.getFormData())
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(response) => this.handleResponse(response)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleResponse(response){
|
||||||
|
if (response.status >= 200 && response.status < 300){
|
||||||
|
this.props.onRequestHide();
|
||||||
|
}
|
||||||
|
else if (response.status >= 400 && response.status < 500) {
|
||||||
|
this.handleError(response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({submitted: false, status: response.status});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleError(response){
|
||||||
|
response.json().then((response) => this.dispatchErrors(response.errors));
|
||||||
|
|
||||||
|
},
|
||||||
|
dispatchErrors(errors){
|
||||||
|
for (var input in errors){
|
||||||
|
if (this.refs && this.refs[input] && this.refs[input].state){
|
||||||
|
this.refs[input].setAlerts(errors[input]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({submitted: false});
|
||||||
|
},
|
||||||
|
render(){
|
||||||
|
let alert = null;
|
||||||
|
if (this.state.status >= 500){
|
||||||
|
alert = <AlertDismissable error="Something went wrong, please try again later"/>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{alert}
|
||||||
|
{this.renderForm()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormMixin;
|
||||||
|
|
@ -7,14 +7,14 @@ let TableColumnMixin = {
|
|||||||
* Generates the bootstrap grid column declarations automatically using
|
* Generates the bootstrap grid column declarations automatically using
|
||||||
* the columnMap.
|
* the columnMap.
|
||||||
*/
|
*/
|
||||||
calcColumnClasses(list, i) {
|
calcColumnClasses(list, i, numOfColumns) {
|
||||||
let bootstrapClasses = ['col-xs-', 'col-sm-', 'col-md-', 'col-lg-'];
|
let bootstrapClasses = ['col-xs-', 'col-sm-', 'col-md-', 'col-lg-'];
|
||||||
|
|
||||||
let listOfRowValues = list.map((column) => column.rowWidth );
|
let listOfRowValues = list.map((column) => column.rowWidth );
|
||||||
let numOfColumns = GeneralUtils.sumNumList(listOfRowValues);
|
let numOfUsedColumns = GeneralUtils.sumNumList(listOfRowValues);
|
||||||
|
|
||||||
if(numOfColumns > 12) {
|
if(numOfUsedColumns > numOfColumns) {
|
||||||
throw new Error('Bootstrap has only 12 columns to assign. You defined ' + numOfColumns + '. Change this in the columnMap you\'re passing to the table.')
|
throw new Error('This table has only ' + numOfColumns + ' columns to assign. You defined ' + numOfUsedColumns + '. Change this in the columnMap you\'re passing to the table.')
|
||||||
} else {
|
} else {
|
||||||
return bootstrapClasses.join( listOfRowValues[i] + ' ') + listOfRowValues[i];
|
return bootstrapClasses.join( listOfRowValues[i] + ' ') + listOfRowValues[i];
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import Router from 'react-router';
|
|||||||
|
|
||||||
import AscribeApp from './components/ascribe_app';
|
import AscribeApp from './components/ascribe_app';
|
||||||
import PieceList from './components/piece_list';
|
import PieceList from './components/piece_list';
|
||||||
import Piece from './components/piece';
|
import EditionContainer from './components/edition_container';
|
||||||
|
|
||||||
let Route = Router.Route;
|
let Route = Router.Route;
|
||||||
|
|
||||||
@ -11,7 +11,9 @@ let Route = Router.Route;
|
|||||||
let routes = (
|
let routes = (
|
||||||
<Route name="app" path="/" handler={AscribeApp}>
|
<Route name="app" path="/" handler={AscribeApp}>
|
||||||
<Route name="pieces" path="/pieces" handler={PieceList}>
|
<Route name="pieces" path="/pieces" handler={PieceList}>
|
||||||
<Route name="piece" path="/pieces/:bitcoin_ID_noPrefix" handler={Piece} />
|
|
||||||
|
</Route>
|
||||||
|
<Route name="edition" path="/editions/:editionId" handler={EditionContainer}>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
import alt from '../alt';
|
import alt from '../alt';
|
||||||
import EditionsListActions from '../actions/edition_list_actions';
|
import EditionsListActions from '../actions/edition_list_actions';
|
||||||
|
|
||||||
@ -7,8 +9,29 @@ class EditionListStore {
|
|||||||
this.bindActions(EditionsListActions);
|
this.bindActions(EditionsListActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateEditionList({pieceId, editionList}) {
|
onUpdateEditionList({pieceId, editionListOfPiece}) {
|
||||||
this.editionList[pieceId] = editionList;
|
if(this.editionList[pieceId]) {
|
||||||
|
this.editionList[pieceId].forEach((edition, i) => {
|
||||||
|
// This uses the index of the new editionList for determining the edition.
|
||||||
|
// If the list of editions can be sorted in the future, this needs to be changed!
|
||||||
|
editionListOfPiece[i] = React.addons.update(edition, {$merge: editionListOfPiece[i]});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.editionList[pieceId] = editionListOfPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectEdition({pieceId, editionId}) {
|
||||||
|
|
||||||
|
this.editionList[pieceId].forEach((edition) => {
|
||||||
|
if(edition.edition_number === editionId) {
|
||||||
|
if(edition.selected) {
|
||||||
|
edition.selected = false;
|
||||||
|
} else {
|
||||||
|
edition.selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
16
js/stores/edition_store.js
Normal file
16
js/stores/edition_store.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import alt from '../alt';
|
||||||
|
import EditionAction from '../actions/edition_actions';
|
||||||
|
|
||||||
|
|
||||||
|
class EditionStore {
|
||||||
|
constructor() {
|
||||||
|
this.edition = {};
|
||||||
|
this.bindActions(EditionAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateEdition(edition) {
|
||||||
|
this.edition = edition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(EditionStore);
|
16
js/stores/piece_store.js
Normal file
16
js/stores/piece_store.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import alt from '../alt';
|
||||||
|
import PieceAction from '../actions/piece_actions';
|
||||||
|
|
||||||
|
|
||||||
|
class PieceStore {
|
||||||
|
constructor() {
|
||||||
|
this.piece = {};
|
||||||
|
this.bindActions(PieceAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdatePiece(piece) {
|
||||||
|
this.piece = piece;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(PieceStore);
|
@ -54,6 +54,13 @@ let FetchApiUtils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return interpolation + orderBy;
|
return interpolation + orderBy;
|
||||||
|
},
|
||||||
|
|
||||||
|
status(response) {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
throw new Error(response.json())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ let GeneralUtils = {
|
|||||||
l.forEach((num) => sum += parseFloat(num) || 0);
|
l.forEach((num) => sum += parseFloat(num) || 0);
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GeneralUtils;
|
export default GeneralUtils;
|
||||||
|
25
package.json
25
package.json
@ -3,26 +3,24 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "Das neue web client for Ascribe",
|
"description": "Das neue web client for Ascribe",
|
||||||
"main": "js/app.js",
|
"main": "js/app.js",
|
||||||
"scripts": {
|
|
||||||
"watch": "watchify -o build/app.js -v -d js/app.js",
|
|
||||||
"build": "browserify . -t [envify --NODE_ENV production] | uglifyjs -cm > build/app.js",
|
|
||||||
"test": "jest"
|
|
||||||
},
|
|
||||||
"author": "Ascribe",
|
"author": "Ascribe",
|
||||||
"license": "Copyright",
|
"license": "Copyright",
|
||||||
"browserify": {
|
|
||||||
"transform": [
|
|
||||||
"babelify",
|
|
||||||
"envify"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-jest": "^4.0.0",
|
"babel-jest": "^4.0.0",
|
||||||
"babelify": "^6.0.2",
|
"babelify": "^6.1.2",
|
||||||
|
"browser-sync": "^2.7.5",
|
||||||
"browserify": "^9.0.8",
|
"browserify": "^9.0.8",
|
||||||
"envify": "^3.4.0",
|
"envify": "^3.4.0",
|
||||||
|
"gulp": "^3.8.11",
|
||||||
|
"gulp-if": "^1.2.5",
|
||||||
|
"gulp-notify": "^2.2.0",
|
||||||
|
"gulp-sourcemaps": "^1.5.2",
|
||||||
|
"gulp-util": "^3.0.4",
|
||||||
"jest-cli": "^0.4.0",
|
"jest-cli": "^0.4.0",
|
||||||
|
"lodash": "^3.9.3",
|
||||||
"reactify": "^1.1.0",
|
"reactify": "^1.1.0",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0",
|
||||||
"watchify": "^3.1.2"
|
"watchify": "^3.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -32,7 +30,8 @@
|
|||||||
"object-assign": "^2.0.0",
|
"object-assign": "^2.0.0",
|
||||||
"react": "^0.13.2",
|
"react": "^0.13.2",
|
||||||
"react-router": "^0.13.3",
|
"react-router": "^0.13.3",
|
||||||
"uglifyjs": "^2.4.10"
|
"uglifyjs": "^2.4.10",
|
||||||
|
"react-bootstrap": "~0.22.6"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"scriptPreprocessor": "node_modules/babel-jest",
|
"scriptPreprocessor": "node_modules/babel-jest",
|
||||||
|
Loading…
Reference in New Issue
Block a user