diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..869e2d3d --- /dev/null +++ b/.eslintrc @@ -0,0 +1,54 @@ +{ + "parser": "babel-eslint", + "env": { + "browser": true, + "es6": true + }, + "rules": { + "quotes": [2, "single"], + "eol-last": [0], + "no-mixed-requires": [0], + "no-underscore-dangle": [0], + "global-strict": [2, "always"], + "no-trailing-spaces": [2, { skipBlankLines: true }], + "no-console": 0, + "camelcase": [2, {"properties": "never"}], + "react/display-name": 0, + "react/jsx-boolean-value": 1, + "react/jsx-no-undef": 1, + "react/jsx-quotes": 1, + "react/jsx-sort-prop-types": 0, + "react/jsx-sort-props": 0, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/no-did-mount-set-state": 1, + "react/no-did-update-set-state": 1, + "react/no-multi-comp": 0, + "react/no-unknown-property": 1, + "react/prop-types": 1, + "react/react-in-jsx-scope": 1, + "react/self-closing-comp": 1, + "react/sort-comp": 1, + "react/wrap-multilines": 1 + }, + "plugins": [ + "react" + ], + "ecmaFeatures": { + "jsx": 1, + "modules": 1, + "arrowFunctions", + "classes": 1, + "blockBindings": 1, + "defaultParams": 1, + "destructuring": 1, + "objectLiteralComputedProperties": 1, + "objectLiteralDuplicateProperties": 0, + "objectLiteralShorthandMethods": 1, + "objectLiteralShorthandProperties": 1, + "restParams": 1, + "spread": 1, + "superInFunctions": 1, + "templateStrings": 1 + } +} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index a685ea56..6e5697bb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,3 +1,5 @@ +'use strict'; + var gulp = require('gulp'); var gulpif = require('gulp-if'); var sourcemaps = require('gulp-sourcemaps'); @@ -12,6 +14,7 @@ var notify = require('gulp-notify'); var sass = require('gulp-sass'); var concat = require('gulp-concat'); var _ = require('lodash'); +var eslint = require('gulp-eslint'); var config = { bootstrapDir: './node_modules/bootstrap-sass' @@ -21,7 +24,7 @@ gulp.task('build', function() { bundle(false); }); -gulp.task('serve', ['browser-sync', 'sass', 'sass:watch', 'copy'], function() { +gulp.task('serve', ['browser-sync', 'lint:watch', 'sass', 'sass:watch', 'copy'], function() { bundle(true); }); @@ -64,6 +67,23 @@ gulp.task('copy', function () { .pipe(gulp.dest('./build/fonts')); }); +gulp.task('lint', function () { + return gulp.src(['js/**/*.js']) + // eslint() attaches the lint output to the eslint property + // of the file object so it can be used by other modules. + .pipe(eslint()) + // eslint.format() outputs the lint results to the console. + // Alternatively use eslint.formatEach() (see Docs). + .pipe(eslint.format()) + // To have the process exit with an error code (1) on + // lint error, return the stream and pipe to failOnError last. + .pipe(eslint.failOnError()); +}); + +gulp.task('lint:watch', function () { + gulp.watch('js/**/*.js', ['lint']); +}); + function bundle(watch) { var bro; diff --git a/index.html b/index.html index 066b36cc..8d16b099 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,6 @@ ascribe - diff --git a/js/actions/edition_actions.js b/js/actions/edition_actions.js index a39c330a..07fcab1d 100644 --- a/js/actions/edition_actions.js +++ b/js/actions/edition_actions.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import EditionFetcher from '../fetchers/edition_fetcher'; diff --git a/js/actions/edition_list_actions.js b/js/actions/edition_list_actions.js index 78cc0eeb..83b9abfe 100644 --- a/js/actions/edition_list_actions.js +++ b/js/actions/edition_list_actions.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import EditionListFetcher from '../fetchers/edition_list_fetcher.js'; @@ -11,13 +13,20 @@ class EditionListActions { ); } - fetchEditionList(pieceId) { + fetchEditionList(pieceId, orderBy, orderAsc) { + if(!orderBy && typeof orderAsc == 'undefined') { + orderBy = 'edition_number'; + orderAsc = true; + } + EditionListFetcher - .fetch(pieceId) + .fetch(pieceId, orderBy, orderAsc) .then((res) => { this.actions.updateEditionList({ 'editionListOfPiece': res.editions, - pieceId + pieceId, + orderBy, + orderAsc }); }) .catch((err) => { diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index 274ef458..cd78569d 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import PieceFetcher from '../fetchers/piece_fetcher'; diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index 0105ff06..938cb195 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import PieceListFetcher from '../fetchers/piece_list_fetcher'; @@ -7,7 +9,8 @@ class PieceListActions { constructor() { this.generateActions( 'updatePieceList', - 'showEditionList' + 'showEditionList', + 'closeAllEditionLists' ); } @@ -27,6 +30,6 @@ class PieceListActions { }); } -}; +} export default alt.createActions(PieceListActions); diff --git a/js/actions/user_actions.js b/js/actions/user_actions.js index 46f63539..90e931b1 100644 --- a/js/actions/user_actions.js +++ b/js/actions/user_actions.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import UserFetcher from '../fetchers/user_fetcher'; @@ -12,12 +14,12 @@ class UserActions { fetchCurrentUser() { UserFetcher.fetchOne() .then((res) => { - this.actions.updateCurrentUser(res['users'][0]); + this.actions.updateCurrentUser(res.users[0]); }) .catch((err) => { console.log(err); }); } -}; +} export default alt.createActions(UserActions); diff --git a/js/alt.js b/js/alt.js index ad49b9b5..94786185 100644 --- a/js/alt.js +++ b/js/alt.js @@ -1,3 +1,5 @@ +'use strict'; + import Alt from 'alt'; export default new Alt(); diff --git a/js/app.js b/js/app.js index d37e1a62..ca2df1c6 100644 --- a/js/app.js +++ b/js/app.js @@ -6,14 +6,31 @@ import promise from 'es6-promise'; promise.polyfill(); -import AscribeApp from './components/ascribe_app'; import AppConstants from './constants/application_constants'; import ApiUrls from './constants/api_urls'; import routes from './routes'; -import alt from './alt'; import fetch from './utils/fetch'; -import AlertDismissable from './components/ascribe_forms/alert'; +/* + Taken from + http://stackoverflow.com/questions/30613447/how-to-debug-reactjss-setstate?noredirect=1#comment49301874_30613447 + + + +*/ + +var warn = console.warn; +console.warn = function(warning) { + if (/(setState)/.test(warning)) { + throw new Error(warning); + } + warn.apply(console, arguments); +}; + +/* + + + */ fetch.defaults({ urlMap: ApiUrls, diff --git a/js/components/ascribe_accordion_list/accordion_list.js b/js/components/ascribe_accordion_list/accordion_list.js index 9eb484b7..a17aed36 100644 --- a/js/components/ascribe_accordion_list/accordion_list.js +++ b/js/components/ascribe_accordion_list/accordion_list.js @@ -1,5 +1,6 @@ +'use strict'; + import React from 'react'; -import ReactAddons from 'react/addons'; let AccordionList = React.createClass({ diff --git a/js/components/ascribe_accordion_list/accordion_list_item.js b/js/components/ascribe_accordion_list/accordion_list_item.js index e9bb8742..e822b5c6 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -1,13 +1,17 @@ -import React from 'react'; +'use strict'; -import AccordionListItemTable from './accordion_list_item_table'; +import React from 'react'; +import Router from 'react-router'; import { getLangText } from '../../utils/lang_utils'; +let Link = Router.Link; + let AccordionListItem = React.createClass({ propTypes: { className: React.PropTypes.string, - content: React.PropTypes.object + content: React.PropTypes.object, + children: React.PropTypes.object }, render() { @@ -15,12 +19,13 @@ let AccordionListItem = React.createClass({
-
+
-
+

{this.props.content.title}

{getLangText('by %s', this.props.content.artist_name)}

+

{this.props.content.date_created.split('-')[0]}

diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table.js b/js/components/ascribe_accordion_list/accordion_list_item_table.js index 1cf410df..4ead1eeb 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table.js @@ -1,20 +1,23 @@ +'use strict'; + import React from 'react'; import Table from '../ascribe_table/table'; import TableItem from '../ascribe_table/table_item'; -import TableColumnContentModel from '../../models/table_column_content_model'; - -import { getLangText } from '../../utils/lang_utils'; +import { ColumnModel } from '../ascribe_table/models/table_models'; let AccordionListItemTable = React.createClass({ propTypes: { className: React.PropTypes.string, parentId: React.PropTypes.number, itemList: React.PropTypes.array, - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), numOfTableItems: React.PropTypes.number, - show: React.PropTypes.bool + show: React.PropTypes.bool, + changeOrder: React.PropTypes.func, + orderBy: React.PropTypes.string, + orderAsc: React.PropTypes.bool }, render() { @@ -22,14 +25,17 @@ let AccordionListItemTable = React.createClass({ return (
+ className="ascribe-table" + columnList={this.props.columnList} + itemList={this.props.itemList} + changeOrder={this.props.changeOrder} + orderBy={this.props.orderBy} + orderAsc={this.props.orderAsc}> {this.props.itemList.map((item, i) => { return ( - + key={i} /> ); })}
diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js index 1d8daa45..c975500e 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js @@ -1,22 +1,26 @@ +'use strict'; + import React from 'react'; +import Router from 'react-router'; import EditionListStore from '../../stores/edition_list_store'; import EditionListActions from '../../actions/edition_list_actions'; -import PieceListStore from '../../stores/piece_list_store'; import PieceListActions from '../../actions/piece_list_actions'; import AccordionListItemTable from './accordion_list_item_table'; import AccordionListItemTableToggle from './accordion_list_item_table_toggle'; +import AccordionListItemTableSelectAllEditionsToggle from './accordion_list_item_table_select_all_editions_toggle'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel, TransitionModel } from '../ascribe_table/models/table_models'; -import TableItemImg from '../ascribe_table/table_item_img'; import TableItemText from '../ascribe_table/table_item_text'; import TableItemCheckbox from '../ascribe_table/table_item_checkbox'; import TableItemAclFiltered from '../ascribe_table/table_item_acl_filtered'; import { getLangText } from '../../utils/lang_utils'; +let Link = Router.Link; + let AccordionListItemTableEditions = React.createClass({ propTypes: { @@ -30,33 +34,116 @@ let AccordionListItemTableEditions = React.createClass({ return EditionListStore.getState(); }, - onChange(state) { - this.setState(state); - }, - componentDidMount() { EditionListStore.listen(this.onChange); }, - componentDidUnmount() { + componentWillUnmount() { EditionListStore.unlisten(this.onChange); }, + onChange(state) { + this.setState(state); + }, + selectItem(pieceId, editionId) { EditionListActions.selectEdition({pieceId, editionId}); }, + selectAllItems() { + this.state.editionList[this.props.parentId] + .forEach((edition) => { + this.selectItem(this.props.parentId, edition.id); + }); + }, + + filterSelectedEditions() { + let selectedEditions = this.state.editionList[this.props.parentId] + .filter((edition) => edition.selected); + return selectedEditions; + }, + toggleTable() { PieceListActions.showEditionList(this.props.parentId); EditionListActions.fetchEditionList(this.props.parentId); }, + changeEditionListOrder(orderBy, orderAsc) { + EditionListActions.fetchEditionList(this.props.parentId, orderBy, orderAsc); + }, + render() { + let selectedEditionsCount = 0; + let allEditionsCount = 0; + let orderBy; + let orderAsc; + + // here we need to check if all editions of a specific + // piece are already defined. Otherwise .length will throw an error and we'll not + // be notified about it. + if(this.state.editionList[this.props.parentId]) { + selectedEditionsCount = this.filterSelectedEditions().length; + allEditionsCount = this.state.editionList[this.props.parentId].length; + orderBy = this.state.editionList[this.props.parentId].orderBy; + orderAsc = this.state.editionList[this.props.parentId].orderAsc; + } + + let transition = new TransitionModel('edition', 'editionId', 'bitcoin_id', PieceListActions.closeAllEditionLists); + let columnList = [ - new TableColumnContentModel((item) => { return { 'editionId': item.id, 'pieceId': this.props.parentId, 'selectItem': this.selectItem, 'selected': item.selected }}, '', '', TableItemCheckbox, 1, false), - new TableColumnContentModel((item) => { return { 'content': item.edition_number }}, 'num_editions', '#', TableItemText, 1, false), - new TableColumnContentModel((item) => { return { 'content': item.bitcoin_id }}, 'bitcoin_id', getLangText('Bitcoin Address'), TableItemText, 5, false), - new TableColumnContentModel((item) => { return { 'content': item.acl }}, 'acl', getLangText('Actions'), TableItemAclFiltered, 4, false) + new ColumnModel( + (item) => { + return { + 'editionId': item.id, + 'pieceId': this.props.parentId, + 'selectItem': this.selectItem, + 'selected': item.selected + }; }, + '', + , + TableItemCheckbox, + 1, + false + ), + new ColumnModel( + (item) => { + return { + 'content': item.edition_number + }; }, + 'edition_number', + '#', + TableItemText, + 1, + true, + transition + ), + new ColumnModel( + (item) => { + return { + 'content': item.bitcoin_id + }; }, + 'bitcoin_id', + getLangText('Bitcoin Address'), + TableItemText, + 5, + true, + transition + ), + new ColumnModel( + (item) => { + return { + 'content': item.acl + }; }, + 'acl', + getLangText('Actions'), + TableItemAclFiltered, + 4, + false, + transition + ) ]; return ( @@ -67,9 +154,12 @@ let AccordionListItemTableEditions = React.createClass({ itemList={this.state.editionList[this.props.parentId]} columnList={columnList} numOfTableItems={this.props.numOfEditions} - show={this.props.show}> + show={this.props.show} + orderBy={orderBy} + orderAsc={orderAsc} + changeOrder={this.changeEditionListOrder}> diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_select_all_editions_toggle.js b/js/components/ascribe_accordion_list/accordion_list_item_table_select_all_editions_toggle.js new file mode 100644 index 00000000..0fe71c35 --- /dev/null +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_select_all_editions_toggle.js @@ -0,0 +1,23 @@ +'use strict'; + +import React from 'react'; + + +let AccordionListItemTableSelectAllEditionsToggle = React.createClass({ + + propTypes: { + onChange: React.PropTypes.func.isRequired, + numOfSelectedEditions: React.PropTypes.number.isRequired, + numOfAllEditions: React.PropTypes.number.isRequired + }, + + render() { + return ( + + ); + } +}); + +export default AccordionListItemTableSelectAllEditionsToggle; \ No newline at end of file diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js b/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js index a3431b04..1f05de4b 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; let AccordionListItemTableToggle = React.createClass({ @@ -10,7 +12,7 @@ let AccordionListItemTableToggle = React.createClass({ render() { return ( - {this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'} diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index bab7de97..6484976c 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import Router from 'react-router'; import Header from '../components/header'; @@ -12,7 +14,7 @@ let AscribeApp = React.createClass({
-
+
); } }); diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index cd9d739d..518a80d6 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import ConsignForm from '../ascribe_forms/form_consign'; @@ -17,31 +19,33 @@ let AclButton = React.createClass({ }, actionProperties(){ - if (this.props.action == 'consign'){ + if (this.props.action === 'consign'){ return { - title: "Consign artwork", - tooltip: "Have someone else sell the artwork", + title: 'Consign artwork', + tooltip: 'Have someone else sell the artwork', form: - } + }; } - else if (this.props.action == 'transfer') { + else if (this.props.action === 'transfer') { return { - title: "Transfer artwork", - tooltip: "Transfer the ownership of the artwork", + title: 'Transfer artwork', + tooltip: 'Transfer the ownership of the artwork', form: - } + }; } - else if (this.props.action == 'loan'){ + else if (this.props.action === 'loan'){ return { - title: "Loan artwork", - tooltip: "Loan your artwork for a limited period of time", - form: } + title: 'Loan artwork', + tooltip: 'Loan your artwork for a limited period of time', + form: + }; } - else if (this.props.action == 'share'){ + else if (this.props.action === 'share'){ return { - title: "Share artwork", - tooltip: "Share the artwork", - form: } + title: 'Share artwork', + tooltip: 'Share the artwork', + form: + }; } }, render() { diff --git a/js/components/ascribe_buttons/button_submit_close.js b/js/components/ascribe_buttons/button_submit_close.js index 377c74c9..094e3c40 100644 --- a/js/components/ascribe_buttons/button_submit_close.js +++ b/js/components/ascribe_buttons/button_submit_close.js @@ -1,5 +1,13 @@ +'use strict'; + import React from 'react'; + +/* + Is this even used somewhere? + Deprecate? 5.6.15 - Tim + + */ let ButtonSubmitOrClose = React.createClass({ render() { if (this.props.submitted){ @@ -14,7 +22,7 @@ let ButtonSubmitOrClose = React.createClass({
- ) + ); } }); diff --git a/js/components/ascribe_forms/alert.js b/js/components/ascribe_forms/alert.js index 482afdfe..4d5bae64 100644 --- a/js/components/ascribe_forms/alert.js +++ b/js/components/ascribe_forms/alert.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import Alert from 'react-bootstrap/lib/Alert'; @@ -7,12 +9,15 @@ let AlertDismissable = React.createClass({ alertVisible: true }; }, + show() { this.setState({alertVisible: true}); }, + hide() { this.setState({alertVisible: false}); }, + render() { if (this.state.alertVisible) { let key = this.props.error; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index e8d8e099..0137e4a0 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; import React from 'react'; @@ -12,19 +12,20 @@ let ConsignForm = React.createClass({ mixins: [FormMixin], url() { - return ApiUrls.ownership_consigns + return ApiUrls.ownership_consigns; }, + getFormData() { return { bitcoin_id: this.getBitcoinIds().join(), consignee: this.refs.consignee.state.value, consign_message: this.refs.consign_message.state.value, password: this.refs.password.state.value - } + }; }, renderForm() { - let title = this.getTitlesString().join(""); + let title = this.getTitlesString().join(''); let username = this.props.currentUser.username; let message = `Hi, diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 12127563..c5269214 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; import React from 'react'; @@ -9,20 +9,26 @@ import InputHidden from './input_hidden'; import InputCheckbox from './input_checkbox'; import InputDate from './input_date'; import InputTextArea from './input_textarea'; -import OwnershipFetcher from '../../fetchers/ownership_fetcher' -import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; + +import OwnershipFetcher from '../../fetchers/ownership_fetcher'; +import ButtonSubmitOrClose from './button_submit_close'; let LoanForm = React.createClass({ + + getInitialState() { + this.setState({ + contract_key: null, + contract_url: null, + loaneeHasContract: false + }); + }, + mixins: [FormMixin], url() { - return ApiUrls.ownership_loans - }, - componentDidMount(){ - this.setState({contract_key: null, - contract_url: null, - loaneeHasContract: false}); + return ApiUrls.ownership_loans; }, + getFormData() { return { bitcoin_id: this.getBitcoinIds().join(), @@ -33,15 +39,18 @@ let LoanForm = React.createClass({ loan_message: this.refs.loan_message.state.value, password: this.refs.password.state.value, terms: this.refs.terms.state.value - } + }; }, - handleLoanEmailBlur(e){ + + handleLoanEmailBlur(){ OwnershipFetcher.fetchLoanContract(this.refs.loanee.state.value) .then((res) => { if (res && res.length > 0) { - this.setState({contract_key: res[0].s3Key, - contract_url: res[0].s3Url, - loaneeHasContract: true}); + this.setState({ + contract_key: res[0].s3Key, + contract_url: res[0].s3Url, + loaneeHasContract: true + }); } else{ this.resetLoanContract(); @@ -52,14 +61,16 @@ let LoanForm = React.createClass({ this.resetLoanContract(); }); }, + resetLoanContract(){ this.setState({contract_key: null, contract_url: null, loaneeHasContract: false }); }, + renderForm() { - let title = this.getTitlesString().join(""); + let title = this.getTitlesString().join(''); let username = this.props.currentUser.username; let message = `Hi, @@ -72,18 +83,19 @@ ${username}`; let contract = ; if (this.state.loaneeHasContract){ - let label =
+ let label = (; - contract = ); + contract = ( + />); } + return (
diff --git a/js/components/ascribe_forms/form_share_email.js b/js/components/ascribe_forms/form_share_email.js index c4fd5e9d..94c2fa51 100644 --- a/js/components/ascribe_forms/form_share_email.js +++ b/js/components/ascribe_forms/form_share_email.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; import React from 'react'; @@ -12,17 +12,19 @@ let ShareForm = React.createClass({ mixins: [FormMixin], url() { - return ApiUrls.ownership_shares_mail + return ApiUrls.ownership_shares_mail; }, + getFormData() { return { bitcoin_id: this.getBitcoinIds().join(), share_emails: this.refs.share_emails.state.value, share_message: this.refs.share_message.state.value - } + }; }, + renderForm() { - let title = this.getTitlesString().join(""); + let title = this.getTitlesString().join(''); let username = this.props.currentUser.username; let message = `Hi, diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js index 48110e49..5fa1e948 100644 --- a/js/components/ascribe_forms/form_transfer.js +++ b/js/components/ascribe_forms/form_transfer.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; import React from 'react'; @@ -9,23 +9,24 @@ import InputTextArea from './input_textarea'; import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; - let TransferForm = React.createClass({ mixins: [FormMixin], url() { - return ApiUrls.ownership_transfers + return ApiUrls.ownership_transfers; }, + getFormData() { return { bitcoin_id: this.getBitcoinIds().join(), transferee: this.refs.transferee.state.value, transfer_message: this.refs.transfer_message.state.value, password: this.refs.password.state.value - } + }; }, + renderForm() { - let title = this.getTitlesString().join(""); + let title = this.getTitlesString().join(''); let username = this.props.currentUser.username; let message = `Hi, diff --git a/js/components/ascribe_forms/form_unconsign.js b/js/components/ascribe_forms/form_unconsign.js index 8c42c2ca..810c70d8 100644 --- a/js/components/ascribe_forms/form_unconsign.js +++ b/js/components/ascribe_forms/form_unconsign.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; import React from 'react'; @@ -12,15 +12,17 @@ let UnConsignForm = React.createClass({ mixins: [FormMixin], url() { - return ApiUrls.ownership_unconsigns + return ApiUrls.ownership_unconsigns; }, + getFormData() { return { bitcoin_id: this.props.edition.bitcoin_id, unconsign_message: this.refs.unconsign_message.state.value, password: this.refs.password.state.value - } + }; }, + renderForm() { let title = this.props.edition.title; let username = this.props.currentUser.username; diff --git a/js/components/ascribe_forms/form_unconsign_request.js b/js/components/ascribe_forms/form_unconsign_request.js index bb467abe..fdf44c94 100644 --- a/js/components/ascribe_forms/form_unconsign_request.js +++ b/js/components/ascribe_forms/form_unconsign_request.js @@ -1,10 +1,9 @@ -import fetch from 'isomorphic-fetch'; +'use strict'; 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 '../ascribe_buttons/button_submit_close'; @@ -12,14 +11,16 @@ let UnConsignRequestForm = React.createClass({ mixins: [FormMixin], url() { - return ApiUrls.ownership_unconsigns_request + return ApiUrls.ownership_unconsigns_request; }, + getFormData() { return { bitcoin_id: this.props.edition.bitcoin_id, unconsign_request_message: this.refs.unconsign_request_message.state.value - } + }; }, + renderForm() { let title = this.props.edition.title; let username = this.props.currentUser.username; diff --git a/js/components/ascribe_forms/input_checkbox.js b/js/components/ascribe_forms/input_checkbox.js index c1ecda1a..187a1acf 100644 --- a/js/components/ascribe_forms/input_checkbox.js +++ b/js/components/ascribe_forms/input_checkbox.js @@ -1,19 +1,24 @@ +'use strict'; + import React from 'react'; -import AlertMixin from '../../mixins/alert_mixin' +import AlertMixin from '../../mixins/alert_mixin'; let InputCheckbox = React.createClass({ - mixins : [AlertMixin], + mixins: [AlertMixin], getInitialState() { - return {value: null, - alerts: null // needed in AlertMixin + return { + value: null, + alerts: null // needed in AlertMixin }; }, + handleChange(event) { this.setState({value: event.target.value}); }, + render() { let alerts = (this.props.submitted) ? null : this.state.alerts; return ( diff --git a/js/components/ascribe_forms/input_date.js b/js/components/ascribe_forms/input_date.js index 401275d4..5ae1221a 100644 --- a/js/components/ascribe_forms/input_date.js +++ b/js/components/ascribe_forms/input_date.js @@ -1,16 +1,19 @@ +'use strict'; + import React from 'react'; -import AlertMixin from '../../mixins/alert_mixin' -import DatePicker from 'react-datepicker/dist/react-datepicker' +import AlertMixin from '../../mixins/alert_mixin'; +import DatePicker from 'react-datepicker/dist/react-datepicker'; let InputDate = React.createClass({ - mixins : [AlertMixin], + mixins: [AlertMixin], getInitialState() { - return {value: null, - value_formatted: null, - alerts: null // needed in AlertMixin + return { + value: null, + value_formatted: null, + alerts: null // needed in AlertMixin }; }, @@ -21,7 +24,7 @@ let InputDate = React.createClass({ }, render: function () { - let className = "form-control input-text-ascribe"; + let className = 'form-control input-text-ascribe'; let alerts = (this.props.submitted) ? null : this.state.alerts; return (
@@ -34,6 +37,24 @@ let InputDate = React.createClass({ placeholderText={this.props.placeholderText}/>
); + // CAN THIS BE REMOVED??? + // + // - Tim? + // + //return ( + //
+ // + // + // + // + //
+ //) } }); diff --git a/js/components/ascribe_forms/input_hidden.js b/js/components/ascribe_forms/input_hidden.js index 96ff95ce..c221372f 100644 --- a/js/components/ascribe_forms/input_hidden.js +++ b/js/components/ascribe_forms/input_hidden.js @@ -1,10 +1,12 @@ +'use strict'; + import React from 'react'; -import AlertMixin from '../../mixins/alert_mixin' +import AlertMixin from '../../mixins/alert_mixin'; let InputHidden = React.createClass({ - mixins : [AlertMixin], + mixins: [AlertMixin], getInitialState() { return {value: this.props.value, diff --git a/js/components/ascribe_forms/input_text.js b/js/components/ascribe_forms/input_text.js index b9e7287a..b35c1ac5 100644 --- a/js/components/ascribe_forms/input_text.js +++ b/js/components/ascribe_forms/input_text.js @@ -1,21 +1,25 @@ +'use strict'; + import React from 'react'; -import AlertMixin from '../../mixins/alert_mixin' +import AlertMixin from '../../mixins/alert_mixin'; let InputText = React.createClass({ - mixins : [AlertMixin], + mixins: [AlertMixin], getInitialState() { return {value: null, alerts: null // needed in AlertMixin }; }, + handleChange(event) { this.setState({value: event.target.value}); }, + render() { - let className = "form-control input-text-ascribe"; + let className = 'form-control input-text-ascribe'; let alerts = (this.props.submitted) ? null : this.state.alerts; return (
diff --git a/js/components/ascribe_forms/input_textarea.js b/js/components/ascribe_forms/input_textarea.js index cf5cfc4a..1d7af441 100644 --- a/js/components/ascribe_forms/input_textarea.js +++ b/js/components/ascribe_forms/input_textarea.js @@ -1,21 +1,24 @@ +'use strict'; + import React from 'react'; -import AlertMixin from '../../mixins/alert_mixin' +import AlertMixin from '../../mixins/alert_mixin'; let InputTextArea = React.createClass({ - mixins : [AlertMixin], + mixins: [AlertMixin], getInitialState() { - return {value: this.props.defaultValue, - alerts: null // needed in AlertMixin + return { + value: this.props.defaultValue, + alerts: null // needed in AlertMixin }; }, handleChange(event) { this.setState({value: event.target.value}); }, render() { - let className = "form-control input-text-ascribe textarea-ascribe-message"; + let className = 'form-control input-text-ascribe textarea-ascribe-message'; let alerts = (this.props.submitted) ? null : this.state.alerts; return ( diff --git a/js/components/ascribe_media/resource_viewer.js b/js/components/ascribe_media/resource_viewer.js index e15e8480..012228c6 100644 --- a/js/components/ascribe_media/resource_viewer.js +++ b/js/components/ascribe_media/resource_viewer.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; @@ -14,7 +16,7 @@ import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; let resourceMap = { 'image': 1 -} +}; let ResourceViewer = React.createClass({ propTypes: { @@ -25,6 +27,7 @@ let ResourceViewer = React.createClass({ mixins: [InjectInHeadMixin], componentDidMount() { + //this.inject('http://antani.com'); }, render() { diff --git a/js/components/ascribe_modal/modal_loan.js b/js/components/ascribe_modal/modal_loan.js index aed475b1..aff34ac0 100644 --- a/js/components/ascribe_modal/modal_loan.js +++ b/js/components/ascribe_modal/modal_loan.js @@ -1,11 +1,13 @@ +'use strict'; + 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 LoanForm from '../ascribe_forms/form_loan' -import ModalMixin from '../../mixins/modal_mixin' +import LoanForm from '../ascribe_forms/form_loan'; +import ModalMixin from '../../mixins/modal_mixin'; let LoanModalButton = React.createClass({ render() { @@ -19,12 +21,12 @@ let LoanModalButton = React.createClass({
- ) + ); } }); let LoanModal = React.createClass({ - mixins : [ModalMixin], + mixins: [ModalMixin], render() { return ( @@ -35,7 +37,7 @@ let LoanModal = React.createClass({ onRequestHide={this.onRequestHide}/>
- ) + ); } }); diff --git a/js/components/ascribe_modal/modal_share.js b/js/components/ascribe_modal/modal_share.js index 4ada244c..2cce53ad 100644 --- a/js/components/ascribe_modal/modal_share.js +++ b/js/components/ascribe_modal/modal_share.js @@ -1,13 +1,13 @@ +'use strict'; + 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 ModalMixin from '../../mixins/modal_mixin' - - -import ShareForm from '../ascribe_forms/form_share_email' +import ModalMixin from '../../mixins/modal_mixin'; +import ShareForm from '../ascribe_forms/form_share_email'; let ShareModalButton = React.createClass({ render() { @@ -20,12 +20,12 @@ let ShareModalButton = React.createClass({
- ) + ); } }); let ShareModal = React.createClass({ - mixins : [ModalMixin], + mixins: [ModalMixin], render() { return ( @@ -36,7 +36,7 @@ let ShareModal = React.createClass({ onRequestHide={this.onRequestHide}/>
- ) + ); } }); diff --git a/js/components/ascribe_modal/modal_unconsign.js b/js/components/ascribe_modal/modal_unconsign.js index 1245fc01..9bfb2faf 100644 --- a/js/components/ascribe_modal/modal_unconsign.js +++ b/js/components/ascribe_modal/modal_unconsign.js @@ -1,11 +1,13 @@ +'use strict'; + 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 UnConsignForm from '../ascribe_forms/form_unconsign' -import ModalMixin from '../../mixins/modal_mixin' +import UnConsignForm from '../ascribe_forms/form_unconsign'; +import ModalMixin from '../../mixins/modal_mixin'; let UnConsignModalButton = React.createClass({ render() { @@ -19,12 +21,12 @@ let UnConsignModalButton = React.createClass({
- ) + ); } }); let UnConsignModal = React.createClass({ - mixins : [ModalMixin], + mixins: [ModalMixin], render() { return ( @@ -35,7 +37,7 @@ let UnConsignModal = React.createClass({ onRequestHide={this.onRequestHide}/> - ) + ); } }); diff --git a/js/components/ascribe_modal/modal_unconsign_request.js b/js/components/ascribe_modal/modal_unconsign_request.js index c74e637b..fa0cd19f 100644 --- a/js/components/ascribe_modal/modal_unconsign_request.js +++ b/js/components/ascribe_modal/modal_unconsign_request.js @@ -1,11 +1,13 @@ +'use strict'; + 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 UnConsignRequestForm from '../ascribe_forms/form_unconsign_request' -import ModalMixin from '../../mixins/modal_mixin' +import UnConsignRequestForm from '../ascribe_forms/form_unconsign_request'; +import ModalMixin from '../../mixins/modal_mixin'; let UnConsignRequestModalButton = React.createClass({ render() { @@ -19,12 +21,12 @@ let UnConsignRequestModalButton = React.createClass({ - ) + ); } }); let UnConsignRequestModal = React.createClass({ - mixins : [ModalMixin], + mixins: [ModalMixin], render() { return ( @@ -35,7 +37,7 @@ let UnConsignRequestModal = React.createClass({ onRequestHide={this.onRequestHide}/> - ) + ); } }); diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index 600d2667..0342bfb6 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import ReactAddons from 'react/addons'; @@ -6,7 +8,7 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import ModalTrigger from 'react-bootstrap/lib/ModalTrigger'; import Tooltip from 'react-bootstrap/lib/Tooltip'; -import ModalMixin from '../../mixins/modal_mixin' +import ModalMixin from '../../mixins/modal_mixin'; let ModalWrapper = React.createClass({ @@ -26,20 +28,22 @@ let ModalWrapper = React.createClass({ {this.props.button} - ) + ); } }); // let ModalBody = React.createClass({ - mixins : [ModalMixin], + + mixins: [ModalMixin], handleSuccess(){ this.props.handleSuccess(); this.props.onRequestHide(); }, + renderChildren() { - return ReactAddons.Children.map(this.props.children, (child, i) => { + return ReactAddons.Children.map(this.props.children, (child) => { return ReactAddons.addons.cloneWithProps(child, { editions: this.props.editions, currentUser: this.props.currentUser, @@ -48,6 +52,7 @@ let ModalBody = React.createClass({ }); }); }, + render() { return ( @@ -55,7 +60,7 @@ let ModalBody = React.createClass({ {this.renderChildren()} - ) + ); } }); diff --git a/js/components/ascribe_pagination/pagination.js b/js/components/ascribe_pagination/pagination.js index e99f8e4e..6b55f119 100644 --- a/js/components/ascribe_pagination/pagination.js +++ b/js/components/ascribe_pagination/pagination.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import PaginationButton from './pagination_button'; @@ -7,25 +9,23 @@ let Pagination = React.createClass({ goToPage: React.PropTypes.func.isRequired, currentPage: React.PropTypes.number.isRequired, totalPages: React.PropTypes.number.isRequired - //itemListCount: React.PropTypes.number.isRequired + //itemListCount: React.PropTypes.number.isRequired }, render() { - return( + return ( ); diff --git a/js/components/ascribe_pagination/pagination_button.js b/js/components/ascribe_pagination/pagination_button.js index bbb2a789..3adf0b50 100644 --- a/js/components/ascribe_pagination/pagination_button.js +++ b/js/components/ascribe_pagination/pagination_button.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import Router from 'react-router'; diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 89599ee6..bbae3dca 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -1,8 +1,15 @@ +'use strict'; + import React from 'react'; +import { mergeOptions } from '../../utils/general_utils'; + import EditionListStore from '../../stores/edition_list_store'; import EditionListActions from '../../actions/edition_list_actions'; +import UserStore from '../../stores/user_store'; +import UserActions from '../../actions/user_actions'; + import PieceListBulkModalSelectedEditionsWidget from './piece_list_bulk_modal_selected_editions_widget'; import AclButtonList from '../ascribe_buttons/acl_button_list'; @@ -13,7 +20,7 @@ let PieceListBulkModal = React.createClass({ }, getInitialState() { - return EditionListStore.getState(); + return mergeOptions(EditionListStore.getState(), UserStore.getState()); }, onChange(state) { @@ -22,14 +29,21 @@ let PieceListBulkModal = React.createClass({ componentDidMount() { EditionListStore.listen(this.onChange); + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); }, - componentDidUnmount() { + componentWillUnmount() { EditionListStore.unlisten(this.onChange); }, - filterForSelected(edition) { - return edition.selected; + fetchSelectedPieceEditionList() { + let filteredPieceIdList = Object.keys(this.state.editionList) + .filter((pieceId) => { + return this.state.editions.editionList[pieceId] + .filter((edition) => edition.selected).length > 0; + }); + return filteredPieceIdList; }, fetchSelectedEditionList() { @@ -37,8 +51,8 @@ let PieceListBulkModal = React.createClass({ Object .keys(this.state.editionList) - .forEach((key) => { - let filteredEditionsForPiece = this.state.editionList[key].filter(this.filterForSelected); + .forEach((pieceId) => { + let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected); selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece); }); @@ -49,10 +63,6 @@ let PieceListBulkModal = React.createClass({ return a.filter((val) => b.indexOf(val) > -1); }, - bulk(action) { - console.log(action); - }, - getAvailableAcls() { let availableAcls = []; let selectedEditionList = this.fetchSelectedEditionList(); @@ -76,8 +86,13 @@ let PieceListBulkModal = React.createClass({ EditionListActions.clearAllEditionSelections(); }, - handleSuccess(){ + handleSuccess() { + this.fetchSelectedPieceEditionList() + .forEach((pieceId) => { + EditionListActions.fetchEditionList(pieceId, this.state.orderBy, this.state.orderAsc); + }); + EditionListActions.clearAllEditionSelections(); }, render() { @@ -95,7 +110,7 @@ let PieceListBulkModal = React.createClass({           - clear all @@ -113,7 +128,7 @@ let PieceListBulkModal = React.createClass({ ); } else { return null; - } + } } }); diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js index 22b35a4a..99ede112 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; let PieceListBulkModalSelectedEditionsWidget = React.createClass({ diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js index ce9150b1..a3c8c219 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import PieceListStore from '../../stores/piece_list_store'; @@ -6,26 +8,28 @@ import PieceListActions from '../../actions/piece_list_actions'; import Input from 'react-bootstrap/lib/Input'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; -import PieceListToolbarFilterWidgetFilter from './piece_list_toolbar_filter_widget'; - let PieceListToolbar = React.createClass({ + propTypes: { + className: React.PropTypes.string + }, + getInitialState() { return PieceListStore.getState(); }, - onChange(state) { - this.setState(state); - }, - componentDidMount() { PieceListStore.listen(this.onChange); }, - componentDidUnmount() { + componentWillUnmount() { PieceListStore.unlisten(this.onChange); }, + onChange(state) { + this.setState(state); + }, + searchFor() { let searchTerm = this.refs.search.getInputDOMNode().value; PieceListActions.fetchPieceList(this.state.page, this.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc); diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index 7aaf9dd8..d9d2b172 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; diff --git a/js/components/ascribe_table/models/table_models.js b/js/components/ascribe_table/models/table_models.js new file mode 100644 index 00000000..e5fad805 --- /dev/null +++ b/js/components/ascribe_table/models/table_models.js @@ -0,0 +1,47 @@ +'use strict'; + +export class ColumnModel { + // ToDo: Add validation for all passed-in parameters + constructor(transformFn, columnName, displayName, displayType, rowWidth, canBeOrdered, transition) { + this.transformFn = transformFn; + this.columnName = columnName; + this.displayName = displayName; + this.displayType = displayType; + this.rowWidth = rowWidth; + this.canBeOrdered = canBeOrdered; + this.transition = transition; + } +} + +/** + * If a user opens an editionList of a piece and clicks on a specific edition to go to the + * piece detail page, all previously opened editionLists are still saved as show = true in the + * pieceList store. + * + * So if the user now comes back to this view the old data will still be in this store, + * since the browser wasn't able to load the new data (only containing show = undefined = false). + * + * This means that without closing all pieces after a transition, we'll get this flickering of editionLists. + * + * Since react-router does not implement a callback function for its transitionTo method, we have to do it + * our selfes, using this TransitionModel. + */ +export class TransitionModel { + constructor(to, queryKey, valueKey, callback) { + this.to = to; + this.queryKey = queryKey; + this.valueKey = valueKey; + this.callback = callback; + } + + toReactRouterLinkProps(queryValue) { + let props = { + to: this.to, + params: {} + }; + + props.params[this.queryKey] = queryValue; + + return props; + } +} \ No newline at end of file diff --git a/js/components/ascribe_table/table.js b/js/components/ascribe_table/table.js index 74f19058..44ba5d61 100644 --- a/js/components/ascribe_table/table.js +++ b/js/components/ascribe_table/table.js @@ -1,15 +1,22 @@ +'use strict'; + import React from 'react'; import ReactAddons from 'react/addons'; import TableHeader from './table_header'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel } from './models/table_models'; let Table = React.createClass({ propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), - changeOrder: React.PropTypes.func + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), + changeOrder: React.PropTypes.func, + orderBy: React.PropTypes.string, + orderAsc: React.PropTypes.bool, + className: React.PropTypes.string, + children: React.PropTypes.array, + itemList: React.PropTypes.array }, renderChildren() { @@ -25,18 +32,17 @@ let Table = React.createClass({ render() { return ( -
- + -
+ {this.renderChildren()} -
-
+ + ); } }); diff --git a/js/components/ascribe_table/table_header.js b/js/components/ascribe_table/table_header.js index 44594394..ccf0f31f 100644 --- a/js/components/ascribe_table/table_header.js +++ b/js/components/ascribe_table/table_header.js @@ -1,25 +1,28 @@ +'use strict'; + import React from 'react'; import TableColumnMixin from '../../mixins/table_column_mixin'; import TableHeaderItem from './table_header_item'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel } from './models/table_models'; let TableHeader = React.createClass({ - mixins: [TableColumnMixin], propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), itemList: React.PropTypes.array.isRequired, changeOrder: React.PropTypes.func, orderAsc: React.PropTypes.bool, orderBy: React.PropTypes.string }, + mixins: [TableColumnMixin], + render() { return ( -
-
+ + {this.props.columnList.map((val, i) => { let columnClasses = this.calcColumnClasses(this.props.columnList, i, 12); @@ -29,20 +32,18 @@ let TableHeader = React.createClass({ return ( - + changeOrder={this.props.changeOrder} /> ); })} -
-
+ + ); - } }); diff --git a/js/components/ascribe_table/table_header_item.js b/js/components/ascribe_table/table_header_item.js index dce2261e..56b5c16c 100644 --- a/js/components/ascribe_table/table_header_item.js +++ b/js/components/ascribe_table/table_header_item.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import TableHeaderItemCarret from './table_header_item_carret'; @@ -6,7 +8,10 @@ let TableHeaderItem = React.createClass({ propTypes: { columnClasses: React.PropTypes.string.isRequired, - displayName: React.PropTypes.string.isRequired, + displayName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.element + ]).isRequired, columnName: React.PropTypes.string.isRequired, canBeOrdered: React.PropTypes.bool, changeOrder: React.PropTypes.func, @@ -22,28 +27,28 @@ let TableHeaderItem = React.createClass({ if(this.props.canBeOrdered && this.props.changeOrder && this.props.orderAsc != null && this.props.orderBy) { if(this.props.columnName === this.props.orderBy) { return ( -
{this.props.displayName} -
+ ); } else { return ( -
{this.props.displayName} -
+ ); } } else { return ( -
+ {this.props.displayName} -
+ ); } } diff --git a/js/components/ascribe_table/table_header_item_carret.js b/js/components/ascribe_table/table_header_item_carret.js index 628ead11..d597e8c5 100644 --- a/js/components/ascribe_table/table_header_item_carret.js +++ b/js/components/ascribe_table/table_header_item_carret.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; let TableHeaderItemCarret = React.createClass({ diff --git a/js/components/ascribe_table/table_item.js b/js/components/ascribe_table/table_item.js index dffb7fa9..a0e6e852 100644 --- a/js/components/ascribe_table/table_item.js +++ b/js/components/ascribe_table/table_item.js @@ -1,6 +1,8 @@ +'use strict'; + import React from 'react'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel } from './models/table_models'; import TableItemWrapper from './table_item_wrapper'; @@ -8,24 +10,17 @@ import TableItemWrapper from './table_item_wrapper'; let TableItem = React.createClass({ propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), columnContent: React.PropTypes.object, - onClick: React.PropTypes.func, // See: https://facebook.github.io/react/tips/expose-component-functions.html className: React.PropTypes.string }, render() { return ( -
-
- - -
-
+ ); } }); diff --git a/js/components/ascribe_table/table_item_acl.js b/js/components/ascribe_table/table_item_acl.js index 87e14761..1f66fd81 100644 --- a/js/components/ascribe_table/table_item_acl.js +++ b/js/components/ascribe_table/table_item_acl.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; diff --git a/js/components/ascribe_table/table_item_acl_filtered.js b/js/components/ascribe_table/table_item_acl_filtered.js index dada05ab..10a419f0 100644 --- a/js/components/ascribe_table/table_item_acl_filtered.js +++ b/js/components/ascribe_table/table_item_acl_filtered.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; @@ -7,8 +9,10 @@ let TableItemAclFiltered = React.createClass({ }, render() { + var availableAcls = ['consign', 'loan', 'transfer', 'view']; + let filteredAcls = this.props.content.filter((v) => { - return v === 'consign' || v === 'loan' || v === 'transfer' || v === 'view'; + return availableAcls.indexOf(v) > -1; }); return ( diff --git a/js/components/ascribe_table/table_item_checkbox.js b/js/components/ascribe_table/table_item_checkbox.js index 02357fa3..131f7427 100644 --- a/js/components/ascribe_table/table_item_checkbox.js +++ b/js/components/ascribe_table/table_item_checkbox.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; diff --git a/js/components/ascribe_table/table_item_img.js b/js/components/ascribe_table/table_item_img.js index c082e85a..d7670524 100644 --- a/js/components/ascribe_table/table_item_img.js +++ b/js/components/ascribe_table/table_item_img.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; /** @@ -5,7 +7,7 @@ import React from 'react'; */ let TableItemImg = React.createClass({ propTypes: { - content: React.PropTypes.string.isRequired, + content: React.PropTypes.string.isRequired }, render() { diff --git a/js/components/ascribe_table/table_item_selectable.js b/js/components/ascribe_table/table_item_selectable.js index 711e42b6..cc53c5f0 100644 --- a/js/components/ascribe_table/table_item_selectable.js +++ b/js/components/ascribe_table/table_item_selectable.js @@ -1,7 +1,9 @@ +'use strict'; + import React from 'react'; import classNames from 'classnames'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel } from './models/table_models'; import TableItem from './table_item'; @@ -9,10 +11,11 @@ import TableItem from './table_item'; let TableItemSelectable = React.createClass({ propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), columnContent: React.PropTypes.object, parentId: React.PropTypes.number, - className: React.PropTypes.string + className: React.PropTypes.string, + selectItem: React.PropTypes.func }, selectItem() { @@ -25,12 +28,11 @@ let TableItemSelectable = React.createClass({ }); return ( - - + onClick={this.selectItem} /> ); } diff --git a/js/components/ascribe_table/table_item_subtable.js b/js/components/ascribe_table/table_item_subtable.js index dcd3da0f..56b22fa0 100644 --- a/js/components/ascribe_table/table_item_subtable.js +++ b/js/components/ascribe_table/table_item_subtable.js @@ -1,6 +1,8 @@ +'use strict'; + import React from 'react'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import { ColumnModel } from './models/table_models'; import EditionListStore from '../../stores/edition_list_store'; import EditionListActions from '../../actions/edition_list_actions'; @@ -16,7 +18,7 @@ import TableItemSubtableButton from './table_item_subtable_button'; let TableItemSubtable = React.createClass({ propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), columnContent: React.PropTypes.object }, @@ -26,14 +28,18 @@ let TableItemSubtable = React.createClass({ }; }, - onChange(state) { - this.setState(state); - }, - componentDidMount() { EditionListStore.listen(this.onChange); }, + componentWillUnmount() { + EditionListStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + loadEditionList() { if(this.state.open) { this.setState({ @@ -61,9 +67,9 @@ let TableItemSubtable = React.createClass({ let renderEditionListTable = () => { let columnList = [ - new TableColumnContentModel('edition_number', 'Number', TableItemText, 2, false), - new TableColumnContentModel('user_registered', 'User', TableItemText, 4, true), - new TableColumnContentModel('acl', 'Actions', TableItemAcl, 4, true) + new ColumnModel('edition_number', 'Number', TableItemText, 2, false), + new ColumnModel('user_registered', 'User', TableItemText, 4, true), + new ColumnModel('acl', 'Actions', TableItemAcl, 4, true) ]; if(this.state.open && this.state.editionList[this.props.columnContent.id] && this.state.editionList[this.props.columnContent.id].length) { @@ -77,10 +83,9 @@ let TableItemSubtable = React.createClass({ className="ascribe-table-item-selectable" selectItem={this.selectItem} parentId={this.props.columnContent.id} - key={i}> - + key={i} /> ); - })} + })} @@ -94,15 +99,13 @@ let TableItemSubtable = React.createClass({ - + columnWidth={12} />
- - +
{renderEditionListTable()} - + ); } }); diff --git a/js/components/ascribe_table/table_item_subtable_button.js b/js/components/ascribe_table/table_item_subtable_button.js index 8c5431d8..75aeba7f 100644 --- a/js/components/ascribe_table/table_item_subtable_button.js +++ b/js/components/ascribe_table/table_item_subtable_button.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; let TableItemSubtableButton = React.createClass({ diff --git a/js/components/ascribe_table/table_item_text.js b/js/components/ascribe_table/table_item_text.js index 0e90a2d5..96dd1f16 100644 --- a/js/components/ascribe_table/table_item_text.js +++ b/js/components/ascribe_table/table_item_text.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; diff --git a/js/components/ascribe_table/table_item_wrapper.js b/js/components/ascribe_table/table_item_wrapper.js index c6251c4b..9e1696f4 100644 --- a/js/components/ascribe_table/table_item_wrapper.js +++ b/js/components/ascribe_table/table_item_wrapper.js @@ -1,19 +1,25 @@ -import React from 'react'; +'use strict'; -import TableColumnContentModel from '../../models/table_column_content_model'; +import React from 'react'; +import Router from 'react-router'; + +import { ColumnModel } from './models/table_models'; import TableColumnMixin from '../../mixins/table_column_mixin'; +let Link = Router.Link; + let TableItemWrapper = React.createClass({ - mixins: [TableColumnMixin], propTypes: { - columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)), + columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)), columnContent: React.PropTypes.object, columnWidth: React.PropTypes.number.isRequired }, + mixins: [TableColumnMixin, Router.Navigation], + render() { return ( -
+ {this.props.columnList.map((column, i) => { let TypeElement = column.displayType; @@ -21,14 +27,35 @@ let TableItemWrapper = React.createClass({ let columnClass = this.calcColumnClasses(this.props.columnList, i, this.props.columnWidth); - return ( -
- -
- ); + if(!column.transition) { + return ( + + + + ); + } else { + let linkProps = column.transition.toReactRouterLinkProps(this.props.columnContent[column.transition.valueKey]); + /** + * If a transition is defined in columnContent, then we can use + * Router.Navigation.transitionTo to redirect the user + * programmatically + */ + return ( + + + + + + ); + } })} -
+ ); } }); diff --git a/js/components/edition.js b/js/components/edition.js index 0a72f8f6..52521a1a 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin'; @@ -5,13 +7,18 @@ import Button from 'react-bootstrap/lib/Button'; import ResourceViewer from './ascribe_media/resource_viewer'; -import EditionActions from '../actions/edition_actions' -import AclButtonList from './ascribe_buttons/acl_button_list' +import EditionActions from '../actions/edition_actions'; +import AclButtonList from './ascribe_buttons/acl_button_list'; /** * This is the component that implements display-specific functionality */ let Edition = React.createClass({ + propTypes: { + edition: React.PropTypes.object, + currentUser: React.PropTypes.object + }, + render() { let thumbnail = this.props.edition.thumbnail; let mimetype = this.props.edition.digital_work.mime; @@ -35,28 +42,39 @@ let Edition = React.createClass({ }); let EditionHeader = React.createClass({ + propTypes: { + edition: React.PropTypes.object + }, + render() { - var title_html =
{this.props.edition.title}
; + var titleHtml =
{this.props.edition.title}
; return (
- + - +
); } }); + let EditionSummary = React.createClass({ + propTypes: { + edition: React.PropTypes.object, + currentUser: React.PropTypes.object + }, + handleSuccess(){ EditionActions.fetchOne(this.props.edition.id); }, + render() { return (
+ value={this.props.edition.edition_number + ' of ' + this.props.edition.num_editions} />
@@ -119,6 +137,14 @@ let CollapsibleParagraph = React.createClass({ let EditionDetailProperty = React.createClass({ + propTypes: { + label: React.PropTypes.string, + value: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.element + ]) + }, + render() { return (
diff --git a/js/components/edition_container.js b/js/components/edition_container.js index 6887fd20..09009248 100644 --- a/js/components/edition_container.js +++ b/js/components/edition_container.js @@ -1,5 +1,9 @@ +'use strict'; + import React from 'react'; +import { mergeOptions } from '../utils/general_utils'; + import EditionActions from '../actions/edition_actions'; import EditionStore from '../stores/edition_store'; import UserActions from '../actions/user_actions'; @@ -11,10 +15,8 @@ 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()} + return mergeOptions(UserStore.getState(), EditionStore.getState()); }, onChange(state) { @@ -22,29 +24,30 @@ let EditionContainer = React.createClass({ }, componentDidMount() { - EditionActions.fetchOne(this.props.params.editionId); EditionStore.listen(this.onChange); - UserActions.fetchCurrentUser(); UserStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); + EditionActions.fetchOne(this.props.params.editionId); }, - componentDidUnmount() { + + componentWillUnmount() { EditionStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); }, render() { - if('title' in this.state.edition) { return ( - + ); } else { return (

Loading

); } - - } }); diff --git a/js/components/fatal_error.js b/js/components/fatal_error.js deleted file mode 100644 index e69de29b..00000000 diff --git a/js/components/header.js b/js/components/header.js index b52f35b4..d46dd4b2 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -1,13 +1,13 @@ +'use strict'; + import React from 'react'; import Router from 'react-router'; -import AltContainer from 'alt/AltContainer'; import UserActions from '../actions/user_actions'; import UserStore from '../stores/user_store'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; -import NavItem from 'react-bootstrap/lib/NavItem'; import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; @@ -23,11 +23,11 @@ let Header = React.createClass({ componentDidMount() { UserActions.fetchCurrentUser(); - UserStore.listen(this.onChange) + UserStore.listen(this.onChange); }, - componentDidUnmount() { - UserStore.unlisten(this.onChange) + componentWillUnmount() { + UserStore.unlisten(this.onChange); }, onChange(state) { diff --git a/js/components/piece_list.js b/js/components/piece_list.js index e6735a27..479b9c3f 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -1,5 +1,6 @@ +'use strict'; + import React from 'react'; -import AltContainer from 'alt/AltContainer'; import PieceListStore from '../stores/piece_list_store'; import PieceListActions from '../actions/piece_list_actions'; @@ -15,7 +16,10 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; let PieceList = React.createClass({ - + propTypes: { + query: React.PropTypes.object + }, + getInitialState() { return PieceListStore.getState(); }, @@ -35,7 +39,7 @@ let PieceList = React.createClass({ }, paginationGoToPage(page) { - return (e) => PieceListActions.fetchPieceList(page, this.state.pageSize, + return () => PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); }, @@ -47,8 +51,8 @@ let PieceList = React.createClass({ render() { let currentPage = parseInt(this.props.query.page, 10) || 1; - let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize) - + let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize); + return (
@@ -64,12 +68,12 @@ let PieceList = React.createClass({ pageSize={this.state.pageSize}> {this.state.pieceList.map((item, i) => { return ( - - @@ -80,8 +84,7 @@ let PieceList = React.createClass({ - + goToPage={this.paginationGoToPage} />
); } diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index db846a72..7d466561 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -1,17 +1,19 @@ +'use strict'; + import AppConstants from './application_constants'; let apiUrls = { - 'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/', - 'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/', + 'ownership_shares_mail': AppConstants.baseUrl + 'ownership/shares/mail/', + 'ownership_transfers': AppConstants.baseUrl + 'ownership/transfers/', 'user': AppConstants.baseUrl + 'users/', 'pieces_list': AppConstants.baseUrl + 'pieces/', 'piece': AppConstants.baseUrl + 'pieces/${piece_id}', 'edition': AppConstants.baseUrl + 'editions/${bitcoin_id}/', 'editions_list': AppConstants.baseUrl + 'pieces/${piece_id}/editions/', - 'ownership_loans' : AppConstants.baseUrl + 'ownership/loans/', - 'ownership_consigns' : AppConstants.baseUrl + 'ownership/consigns/', - 'ownership_unconsigns' : AppConstants.baseUrl + 'ownership/unconsigns/', - 'ownership_unconsigns_request' : AppConstants.baseUrl + 'ownership/unconsigns/request/' + 'ownership_loans': AppConstants.baseUrl + 'ownership/loans/', + 'ownership_consigns': AppConstants.baseUrl + 'ownership/consigns/', + 'ownership_unconsigns': AppConstants.baseUrl + 'ownership/unconsigns/', + 'ownership_unconsigns_request': AppConstants.baseUrl + 'ownership/unconsigns/request/' }; export default apiUrls; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index b235d1d4..25486919 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -1,3 +1,5 @@ +'use strict'; + let constants = { //'baseUrl': 'http://localhost:8000/api/', 'baseUrl': 'http://staging.ascribe.io/api/', diff --git a/js/constants/languages.js b/js/constants/languages.js index 47e29ee0..f673083d 100644 --- a/js/constants/languages.js +++ b/js/constants/languages.js @@ -1,3 +1,5 @@ +'use strict'; + const languages = { 'en-US': { 'Bitcoin Address': 'Bitcoin Address', diff --git a/js/fetchers/edition_fetcher.js b/js/fetchers/edition_fetcher.js index 46cdf1b7..bb995523 100644 --- a/js/fetchers/edition_fetcher.js +++ b/js/fetchers/edition_fetcher.js @@ -1,13 +1,11 @@ +'use strict'; + import fetch from '../utils/fetch'; -import AppConstants from '../constants/application_constants'; - - let EditionFetcher = { /** * Fetch one user from the API. * If no arg is supplied, load the current user - * */ fetchOne(editionId) { return fetch.get('edition', {'bitcoin_id': editionId}); diff --git a/js/fetchers/edition_list_fetcher.js b/js/fetchers/edition_list_fetcher.js index 07e52d87..21ebee58 100644 --- a/js/fetchers/edition_list_fetcher.js +++ b/js/fetchers/edition_list_fetcher.js @@ -1,14 +1,17 @@ +'use strict'; + import fetch from '../utils/fetch'; -import AppConstants from '../constants/application_constants'; +import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; let EditionListFetcher = { /** * Fetches a list of editions from the API. */ - fetch(pieceId) { - return fetch.get('editions_list', { 'piece_id': pieceId }); + fetch(pieceId, orderBy, orderAsc) { + let ordering = generateOrderingQueryParams(orderBy, orderAsc); + return fetch.get('editions_list', { 'piece_id': pieceId, ordering }); } }; diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js index 94050860..57cef4b9 100644 --- a/js/fetchers/ownership_fetcher.js +++ b/js/fetchers/ownership_fetcher.js @@ -1,14 +1,14 @@ +'use strict'; + import fetch from 'isomorphic-fetch'; import AppConstants from '../constants/application_constants'; -import FetchApiUtils from '../utils/fetch_api_utils'; let OwnershipFetcher = { /** * Fetch one user from the API. * If no arg is supplied, load the current user - * */ fetchLoanContract(email) { return fetch(AppConstants.baseUrl + 'ownership/loans/contract/?loanee=' + email, { diff --git a/js/fetchers/piece_fetcher.js b/js/fetchers/piece_fetcher.js index cf37c4d3..a5317e5f 100644 --- a/js/fetchers/piece_fetcher.js +++ b/js/fetchers/piece_fetcher.js @@ -1,15 +1,14 @@ -import fetch from '../utils/fetch'; +'use strict'; -import AppConstants from '../constants/application_constants'; +import fetch from '../utils/fetch'; let PieceFetcher = { /** * Fetch one user from the API. * If no arg is supplied, load the current user - * */ - fetchOne(pieceId) { + fetchOne() { return fetch.get('piece'); } }; diff --git a/js/fetchers/piece_list_fetcher.js b/js/fetchers/piece_list_fetcher.js index ed867253..2f706e15 100644 --- a/js/fetchers/piece_list_fetcher.js +++ b/js/fetchers/piece_list_fetcher.js @@ -1,4 +1,5 @@ -import AppConstants from '../constants/application_constants'; +'use strict'; + import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; import fetch from '../utils/fetch'; diff --git a/js/fetchers/user_fetcher.js b/js/fetchers/user_fetcher.js index df3e887e..c57c93d9 100644 --- a/js/fetchers/user_fetcher.js +++ b/js/fetchers/user_fetcher.js @@ -1,13 +1,12 @@ -import fetch from '../utils/fetch'; +'use strict'; -import AppConstants from '../constants/application_constants'; +import fetch from '../utils/fetch'; let UserFetcher = { /** * Fetch one user from the API. * If no arg is supplied, load the current user - * */ fetchOne() { return fetch.get('user'); diff --git a/js/mixins/alert_mixin.js b/js/mixins/alert_mixin.js index f52890c7..2b9b5158 100644 --- a/js/mixins/alert_mixin.js +++ b/js/mixins/alert_mixin.js @@ -1,15 +1,17 @@ +'use strict'; + import React from 'react'; import AlertDismissable from '../components/ascribe_forms/alert'; let AlertMixin = { setAlerts(errors){ - let alerts = errors.map( - function(error) { - return ; - }.bind(this) - ); + let alerts = errors.map((error) => { + return ; + }); + this.setState({alerts: alerts}); }, + clearAlerts(){ this.setState({alerts: null}); } diff --git a/js/mixins/form_mixin.js b/js/mixins/form_mixin.js index 365e2413..98876ddb 100644 --- a/js/mixins/form_mixin.js +++ b/js/mixins/form_mixin.js @@ -1,15 +1,17 @@ +'use strict'; + import fetch from '../utils/fetch'; import React from 'react'; -import AppConstants from '../constants/application_constants' -import AlertDismissable from '../components/ascribe_forms/alert' +import AlertDismissable from '../components/ascribe_forms/alert'; export const FormMixin = { getInitialState() { return { submitted: false, + status: null, errors: [] - } + }; }, submit(e) { @@ -18,7 +20,7 @@ export const FormMixin = { this.clearErrors(); fetch .post(this.url(), { body: this.getFormData() }) - .then(response => { this.props.handleSuccess(); }) + .then(() => { this.props.handleSuccess(); }) .catch(this.handleError); }, clearErrors(){ @@ -45,30 +47,30 @@ export const FormMixin = { getBitcoinIds(){ return this.props.editions.map(function(edition){ - return edition.bitcoin_id - }) + return edition.bitcoin_id; + }); }, getTitlesString(){ return this.props.editions.map(function(edition){ - return '- \"' + edition.title + ', edition ' + edition.edition_number + '\"\n' - }) + return '- \"' + edition.title + ', edition ' + edition.edition_number + '\"\n'; + }); }, render(){ let alert = null; if (this.state.errors.length > 0){ - alert = - this.state.errors.map(function(error) { - return ; - }.bind(this)); + alert = this.state.errors.map((error) => { + return ; + }); } + return (
{alert} {this.renderForm()}
- ) + ); } }; diff --git a/js/mixins/inject_in_head_mixin.js b/js/mixins/inject_in_head_mixin.js index 72ecdfe1..42e3b6dc 100644 --- a/js/mixins/inject_in_head_mixin.js +++ b/js/mixins/inject_in_head_mixin.js @@ -1,12 +1,14 @@ +'use strict'; + let mapAttr = { link: 'href', source: 'src' -} +}; let mapExt = { js: 'source', css: 'link' -} +}; let InjectInHeadMixin = { @@ -23,9 +25,9 @@ let InjectInHeadMixin = { }, injectTag(tag, src){ - console.log(this.foobar); - if (InjectInHeadMixin.isPresent(tag, src)) + if (InjectInHeadMixin.isPresent(tag, src)) { return; + } let attr = mapAttr[tag]; let element = document.createElement(tag); @@ -42,13 +44,14 @@ let InjectInHeadMixin = { }, inject(src) { - //debugger; let ext = src.split('.').pop(); try { let tag = mapAttr(src); } catch (e) { throw new Error(`Cannot inject ${src} in the DOM, cannot guess the tag name from extension ${ext}. Valid extensions are "js" and "css".`); } + // ES6Lint says tag is not defined, pls fix + // - Tim InjectInHeadMixin.injectTag(tag, src); } diff --git a/js/mixins/modal_mixin.js b/js/mixins/modal_mixin.js index 7dee243b..6087f32c 100644 --- a/js/mixins/modal_mixin.js +++ b/js/mixins/modal_mixin.js @@ -1,9 +1,10 @@ -import React from 'react'; +'use strict'; let ModalMixin = { onRequestHide(e){ - if (e) + if (e) { e.preventDefault(); + } this.props.onRequestHide(); } }; diff --git a/js/mixins/table_column_mixin.js b/js/mixins/table_column_mixin.js index eac266b6..b8813433 100644 --- a/js/mixins/table_column_mixin.js +++ b/js/mixins/table_column_mixin.js @@ -1,4 +1,4 @@ -import React from 'react'; +'use strict'; import { sumNumList } from '../utils/general_utils'; @@ -14,7 +14,7 @@ let TableColumnMixin = { let numOfUsedColumns = sumNumList(listOfRowValues); if(numOfUsedColumns > numOfColumns) { - 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.') + 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 { return bootstrapClasses.join( listOfRowValues[i] + ' ') + listOfRowValues[i]; } diff --git a/js/models/table_column_content_model.js b/js/models/table_column_content_model.js deleted file mode 100644 index 5e93cce4..00000000 --- a/js/models/table_column_content_model.js +++ /dev/null @@ -1,13 +0,0 @@ -class TableColumnContentModel { - // ToDo: Add validation for all passed-in parameters - constructor(transformFn, columnName, displayName, displayType, rowWidth, canBeOrdered) { - this.transformFn = transformFn; - this.columnName = columnName; - this.displayName = displayName; - this.displayType = displayType; - this.rowWidth = rowWidth; - this.canBeOrdered = canBeOrdered; - } -} - -export default TableColumnContentModel; \ No newline at end of file diff --git a/js/routes.js b/js/routes.js index ab515462..eeadc564 100644 --- a/js/routes.js +++ b/js/routes.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import Router from 'react-router'; @@ -10,11 +12,8 @@ let Route = Router.Route; let routes = ( - - - - - + + ); diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js index b8855896..67993ff9 100644 --- a/js/stores/edition_list_store.js +++ b/js/stores/edition_list_store.js @@ -1,3 +1,5 @@ +'use strict'; + import React from 'react'; import alt from '../alt'; @@ -9,15 +11,24 @@ class EditionListStore { this.bindActions(EditionsListActions); } - onUpdateEditionList({pieceId, editionListOfPiece}) { + onUpdateEditionList({pieceId, editionListOfPiece, orderBy, orderAsc}) { 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; + + /** + * orderBy and orderAsc are specific to a single list of editons + * therefore they need to be saved in relation to their parent-piece. + * + * Default values for both are set in the editon_list-actions. + */ + this.editionList[pieceId].orderBy = orderBy; + this.editionList[pieceId].orderAsc = orderAsc; } onSelectEdition({pieceId, editionId}) { @@ -46,6 +57,6 @@ class EditionListStore { }); }); } -}; +} export default alt.createStore(EditionListStore); \ No newline at end of file diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js index b33e248f..7579c8d8 100644 --- a/js/stores/edition_store.js +++ b/js/stores/edition_store.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import EditionActions from '../actions/edition_actions'; diff --git a/js/stores/piece_list_store.js b/js/stores/piece_list_store.js index 7ca96c87..d61d6c07 100644 --- a/js/stores/piece_list_store.js +++ b/js/stores/piece_list_store.js @@ -1,11 +1,14 @@ +'use strict'; + import alt from '../alt'; + import PieceListActions from '../actions/piece_list_actions'; class PieceListStore { constructor() { /** - * The store manages the state that is introduced by fetching + * The store manages the state that is introduced by fetching * the resource with certain parameters. * * This means that pieceList for example only contains pageSize-many items. @@ -18,8 +21,8 @@ class PieceListStore { this.pieceListCount = 0; this.page = 1; this.pageSize = 10; - this.search = ""; - this.orderBy = "artist_name"; + this.search = ''; + this.orderBy = 'artist_name'; this.orderAsc = true; this.bindActions(PieceListActions); } @@ -37,15 +40,40 @@ class PieceListStore { }); } + onCloseAllEditionLists() { + this.pieceList + .forEach((piece) => { + piece.show = false; + }); + } + onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount }) { this.page = page; this.pageSize = pageSize; this.search = search; this.orderAsc = orderAsc; this.orderBy = orderBy; - this.pieceList = pieceList; this.pieceListCount = pieceListCount; + + /** + * Pagination - Known Issue: + * ######################### + * + * + * The piece list store currently stores the open/close state of a piece list item. + * + * Once a new page is requested, this.pieceList will be overwritten, which means that the + * open/close state of a specific list item will be thrown away. + * + * This means that when opening an editionListTable on a piece, and continuing + * clicking next or back in the pagination, the editionListTable will return to its + * default value, which is "close". + * + * We did not implement this, as we're going to add pagination to pieceList at some + * point anyway. Then, this problem is automatically resolved. + */ + this.pieceList = pieceList; } -}; +} export default alt.createStore(PieceListStore); diff --git a/js/stores/piece_store.js b/js/stores/piece_store.js index 35e8c229..f9693564 100644 --- a/js/stores/piece_store.js +++ b/js/stores/piece_store.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import PieceAction from '../actions/piece_actions'; diff --git a/js/stores/user_store.js b/js/stores/user_store.js index 225c1590..819346ef 100644 --- a/js/stores/user_store.js +++ b/js/stores/user_store.js @@ -1,3 +1,5 @@ +'use strict'; + import alt from '../alt'; import UserActions from '../actions/user_actions'; diff --git a/js/utils/fetch.js b/js/utils/fetch.js index f7e12f43..ec674935 100644 --- a/js/utils/fetch.js +++ b/js/utils/fetch.js @@ -1,11 +1,13 @@ +'use strict'; + import { default as _fetch } from 'isomorphic-fetch'; import { argsToQueryParams } from '../utils/fetch_api_utils'; -class UrlMapError extends Error {}; -class ServerError extends Error {}; -class APIError extends Error {}; +class UrlMapError extends Error {} +class ServerError extends Error {} +class APIError extends Error {} class Fetch { @@ -33,7 +35,7 @@ class Fetch { } handleAPIError(json) { - if (!json['success']) { + if (!json.success) { let error = new APIError(); error.json = json; throw error; @@ -58,7 +60,7 @@ class Fetch { let re = /\${(\w+)}/g; newUrl = newUrl.replace(re, (match, key) => { - let val = params[key] + let val = params[key]; if (!val) { throw new Error(`Cannot find param ${key}`); } @@ -76,7 +78,7 @@ class Fetch { request(verb, url, options) { options = options || {}; let merged = this._merge(this.httpOptions, options); - merged['method'] = verb; + merged.method = verb; return _fetch(url, merged) .then(this.unpackResponse) .then(JSON.parse) @@ -95,8 +97,8 @@ class Fetch { let newUrl = this.prepareUrl(url, params); let body = null; - if (params['body']) { - body = JSON.stringify(params['body']) + if (params.body) { + body = JSON.stringify(params.body); } return this.request('post', url, { body }); } diff --git a/js/utils/fetch_api_utils.js b/js/utils/fetch_api_utils.js index bf66a45c..272e29f3 100644 --- a/js/utils/fetch_api_utils.js +++ b/js/utils/fetch_api_utils.js @@ -1,3 +1,5 @@ +'use strict'; + import { sanitize } from './general_utils'; // TODO: Create Unittests that test all functions @@ -16,7 +18,7 @@ import { sanitize } from './general_utils'; * ?page=1&page_size=10 * * CamelCase gets converted to snake_case! - * + * */ export function argsToQueryParams(obj) { @@ -38,10 +40,10 @@ export function argsToQueryParams(obj) { return s + snakeCaseKey + '=' + encodeURIComponent(obj[key]); }) .join(''); -}; +} /** - * Takes a string and a boolean and generates a string query parameter for + * Takes a string and a boolean and generates a string query parameter for * an API call. */ export function generateOrderingQueryParams(orderBy, orderAsc) { @@ -52,11 +54,11 @@ export function generateOrderingQueryParams(orderBy, orderAsc) { } return interpolation + orderBy; -}; +} export function status(response) { if (response.status >= 200 && response.status < 300) { - return response + return response; } - throw new Error(response.json()) -}; + throw new Error(response.json()); +} diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 96f56c1e..4caf3d15 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -1,3 +1,5 @@ +'use strict'; + // TODO: Create Unittests that test all functions export function sanitize(obj) { @@ -12,7 +14,7 @@ export function sanitize(obj) { }); return obj; -}; +} /** * Returns the values of an object. @@ -21,7 +23,7 @@ export function valuesOfObject(obj) { return Object .keys(obj) .map(key => obj[key]); -}; +} /** * Sums up a list of numbers. Like a Epsilon-math-kinda-sum... @@ -30,11 +32,14 @@ export function sumNumList(l) { let sum = 0; l.forEach((num) => sum += parseFloat(num) || 0); return sum; -}; +} /* Taken from http://stackoverflow.com/a/4795914/1263876 Behaves like C's format string function + + REFACTOR TO ES6 (let instead of var) + */ export function formatText() { var args = arguments, @@ -60,4 +65,34 @@ export function formatText() { } return val; }); -}; +} + +/** + * Takes a list of object and merges their keys to one object. + * Uses mergeOptions for two objects. + * @param {[type]} l [description] + * @return {[type]} [description] + */ +export function mergeOptions(...l) { + let newObj = {}; + + for(let i = 1; i < l.length; i++) { + newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i])); + } + + return newObj; +} + +/** + * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 + * @param obj1 + * @param obj2 + * @returns obj3 a new object based on obj1 and obj2 + * Taken from: http://stackoverflow.com/a/171256/1263876 + */ +function _mergeOptions(obj1, obj2){ + let obj3 = {}; + for (let attrname in obj1) { obj3[attrname] = obj1[attrname]; } + for (let attrname in obj2) { obj3[attrname] = obj2[attrname]; } + return obj3; +} diff --git a/js/utils/lang_utils.js b/js/utils/lang_utils.js index 7eb4b6f3..bf842efa 100644 --- a/js/utils/lang_utils.js +++ b/js/utils/lang_utils.js @@ -1,3 +1,5 @@ +'use strict'; + import languages from '../constants/languages'; import { formatText } from './general_utils'; @@ -11,7 +13,7 @@ import { formatText } from './general_utils'; export function getLangText(s, ...args) { let lang = navigator.language || navigator.userLanguage; // this is just for testing, as changing the navigator.language wasn't possible - lang = 'de'; + //lang = 'de'; try { if(lang in languages) { return formatText(languages[lang][s], args); @@ -27,4 +29,4 @@ export function getLangText(s, ...args) { } } -}; +} diff --git a/package.json b/package.json index 99f8f431..5de03b4e 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,25 @@ "version": "0.0.1", "description": "Das neue web client for Ascribe", "main": "js/app.js", + "scripts": { + "lint": "eslint ./js" + }, "author": "Ascribe", "license": "Copyright", "private": true, "devDependencies": { + "babel-eslint": "^3.1.11", "babel-jest": "^4.0.0", "babelify": "^6.1.2", "bootstrap-sass": "^3.3.4", "browser-sync": "^2.7.5", "browserify": "^9.0.8", "envify": "^3.4.0", + "eslint": "^0.22.1", + "eslint-plugin-react": "^2.5.0", "gulp": "^3.8.11", "gulp-concat": "^2.5.2", + "gulp-eslint": "^0.13.2", "gulp-if": "^1.2.5", "gulp-notify": "^2.2.0", "gulp-sass": "^2.0.1", @@ -37,7 +44,6 @@ "react-bootstrap": "~0.22.6", "react-router": "^0.13.3", "uglifyjs": "^2.4.10", - "react-bootstrap": "~0.22.6", "react-datepicker": "~0.8.0" }, "jest": { diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index 43b77fa9..21236488 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -1,4 +1,4 @@ -$ascribe-accordion-list-item-height: 9em; +$ascribe-accordion-list-item-height: 8em; $ascribe-accordion-list-font: 'Source Sans Pro'; .ascribe-accordion-list-item { @@ -21,29 +21,20 @@ $ascribe-accordion-list-font: 'Source Sans Pro'; height:100%; // ToDo: Include media queries for thumbnail .thumbnail-wrapper { - float:left; - height:100%; - width:$ascribe-accordion-list-item-height; - overflow:hidden; + margin-left:0; padding-left:0; - padding-right:0; - img { + display:block; height: $ascribe-accordion-list-item-height; } } - .info-wrapper { - float:left; - font-family: $ascribe-accordion-list-font; - margin-left: 2em; - padding-top: .75em; - h1 { - font-size: 2.25em; - } - h3 { - font-size: 1.1em; - margin: .7em 0 0 0; - } + h1 { + margin-top: .3em; + font-size: 2.25em; + } + h3 { + font-size: 1.1em; + margin: .7em 0 0 0; } } } diff --git a/sass/ascribe_piece_list_bulk_modal.scss b/sass/ascribe_piece_list_bulk_modal.scss index f1d26a9e..d2c72113 100644 --- a/sass/ascribe_piece_list_bulk_modal.scss +++ b/sass/ascribe_piece_list_bulk_modal.scss @@ -11,7 +11,7 @@ border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; border-bottom: 0.2em solid #E0E0E0; - z-index:1041; // between overlay and modal + z-index:1000; } .piece-list-bulk-modal-clear-all { diff --git a/sass/main.scss b/sass/main.scss index 34903cc4..cc006d33 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -3,6 +3,7 @@ @import 'variables'; @import 'ascribe_variables'; @import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap'; +@import '../node_modules/react-datepicker/dist/react-datepicker'; @import './ascribe-fonts/style'; @import './ascribe-fonts/ascribe-fonts'; @import 'ascribe_accordion_list'; @@ -36,15 +37,8 @@ float: none; } -.ascribe-table-header-row { - border-bottom: 2px solid #E0E0E0; - padding: 0; -} - -.ascribe-table-header-column { - display: table; - height:3em; - padding: 0; +.ascribe-table { + margin-bottom:0; } .ascribe-table-header-column > span { @@ -58,14 +52,6 @@ .ascribe-table-header-column > span > .glyphicon { font-size: .5em; } -/* -.ascribe-table-item:nth-child(even) { - background-color: #F5F5F5; -}*/ - -/*.ascribe-table-item:hover { - background-color: #EEEEEE; -}*/ .ascribe-table-item-column { display: table; @@ -77,9 +63,9 @@ .ascribe-table-item-column > * { display: table-cell; vertical-align: middle; - text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; } .ascribe-table-item-selected {