1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00

Merge remote-tracking branch 'origin/master' into AD-901-add-lint-for-scss

Conflicts:
	sass/main.scss
This commit is contained in:
vrde 2015-08-31 19:09:09 +02:00
commit d2adc7a388
26 changed files with 444 additions and 183 deletions

View File

@ -24,8 +24,11 @@ gulp serve
Additionally, to work on the white labeling functionality, you need to edit your `/etc/hosts` file and add: Additionally, to work on the white labeling functionality, you need to edit your `/etc/hosts` file and add:
``` ```
127.0.0.1 localhost.com 127.0.0.1 localhost.com
127.0.0.1 cc.localhost.com 127.0.0.1 cc.localhost.com
127.0.0.1 cyland.localhost.com
127.0.0.1 ikonotv.localhost.com
127.0.0.1 sluice.localhost.com
``` ```

View File

@ -23,7 +23,7 @@ var argv = require('yargs').argv;
var server = require('./server.js').app; var server = require('./server.js').app;
var minifyCss = require('gulp-minify-css'); var minifyCss = require('gulp-minify-css');
var uglify = require('gulp-uglify'); var uglify = require('gulp-uglify');
var opn = require('opn');
var config = { var config = {
@ -48,8 +48,7 @@ var config = {
}, },
filesToWatch: [ filesToWatch: [
'build/css/*.css', 'build/css/*.css',
'build/js/*.js', 'build/js/*.js'
'node_modules/react-s3-fine_uploader/*.js'
] ]
}; };
@ -73,6 +72,9 @@ gulp.task('js:build', function() {
gulp.task('serve', ['browser-sync', 'run-server', 'sass:build', 'sass:watch', 'copy'], function() { gulp.task('serve', ['browser-sync', 'run-server', 'sass:build', 'sass:watch', 'copy'], function() {
bundle(true); bundle(true);
// opens the browser window with the correct url, which is localhost.com
opn('http://www.localhost.com:3000');
}); });
gulp.task('jest', function(done) { gulp.task('jest', function(done) {
@ -93,7 +95,9 @@ gulp.task('browser-sync', function() {
browserSync({ browserSync({
files: config.filesToWatch, files: config.filesToWatch,
proxy: 'http://localhost:4000', proxy: 'http://localhost:4000',
port: 3000 port: 3000,
open: false, // does not open the browser-window anymore (handled manually)
ghostMode: false
}); });
}); });

View File

@ -25,8 +25,9 @@ class PieceListActions {
orderBy, orderBy,
orderAsc, orderAsc,
filterBy, filterBy,
'pieceList': [], pieceList: [],
'pieceListCount': -1 pieceListCount: -1,
unfilteredPieceListCount: -1
}); });
// afterwards, we can load the list // afterwards, we can load the list
@ -42,8 +43,9 @@ class PieceListActions {
orderBy, orderBy,
orderAsc, orderAsc,
filterBy, filterBy,
'pieceList': res.pieces, pieceList: res.pieces,
'pieceListCount': res.count pieceListCount: res.count,
unfilteredPieceListCount: res.unfiltered_count
}); });
resolve(); resolve();
}) })

View File

@ -6,15 +6,16 @@ import Header from '../components/header';
import Footer from '../components/footer'; import Footer from '../components/footer';
import GlobalNotification from './global_notification'; import GlobalNotification from './global_notification';
// let Link = Router.Link; import getRoutes from '../routes';
let RouteHandler = Router.RouteHandler;
let RouteHandler = Router.RouteHandler;
let AscribeApp = React.createClass({ let AscribeApp = React.createClass({
render() { render() {
return ( return (
<div className="container ascribe-default-app"> <div className="container ascribe-default-app">
<Header /> <Header routes={getRoutes()} />
<RouteHandler /> <RouteHandler />
<Footer /> <Footer />
<GlobalNotification /> <GlobalNotification />

View File

@ -84,6 +84,8 @@ let PieceContainer = React.createClass({
IT SHOULD BE REMOVED AND REPLACED WITH A BETTER SOLUTION ASAP! IT SHOULD BE REMOVED AND REPLACED WITH A BETTER SOLUTION ASAP!
ALSO, WE ENABLED THE LOAN BUTTON FOR IKONOTV TO LET THEM LOAN ON A PIECE LEVEL
*/ */
if(state && state.piece && state.piece.acl && typeof state.piece.acl.acl_loan !== 'undefined') { if(state && state.piece && state.piece.acl && typeof state.piece.acl.acl_loan !== 'undefined') {
@ -202,6 +204,7 @@ let PieceContainer = React.createClass({
); );
} }
}, },
render() { render() {
if('title' in this.state.piece) { if('title' in this.state.piece) {
return ( return (

View File

@ -2,20 +2,21 @@
import React from 'react'; import React from 'react';
import Property from '../../../../../ascribe_forms/property'; import LoanContractListActions from '../../actions/loan_contract_list_actions';
import LoanContractListStore from '../../stores/loan_contract_list_store';
import LoanContractListActions from '../../../../../../actions/loan_contract_list_actions'; import GlobalNotificationModel from '../../models/global_notification_model';
import LoanContractListStore from '../../../../../../stores/loan_contract_list_store'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import GlobalNotificationModel from '../../../../../../models/global_notification_model'; import Form from './form';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; import Property from './property';
import PropertyCollapsible from './property_collapsible';
import InputTextAreaToggable from './input_textarea_toggable';
import Form from '../../../../../ascribe_forms/form'; import ApiUrls from '../../constants/api_urls';
import ApiUrls from '../../../../../../constants/api_urls'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../../utils/general_utils';
let ContractForm = React.createClass({ let ContractForm = React.createClass({
@ -96,7 +97,7 @@ let ContractForm = React.createClass({
buttons={<button buttons={<button
type="submit" type="submit"
className="btn ascribe-btn ascribe-btn-login"> className="btn ascribe-btn ascribe-btn-login">
{getLangText('SEND LOAN REQUEST')} {getLangText('Send loan request')}
</button>} </button>}
spinner={ spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
@ -104,7 +105,7 @@ let ContractForm = React.createClass({
</span> </span>
}> }>
<div className="ascribe-form-header"> <div className="ascribe-form-header">
<h3>{getLangText('CONTRACT FORM')}</h3> <h3>{getLangText('Contract form')}</h3>
</div> </div>
<Property <Property
name='artist_name' name='artist_name'
@ -123,14 +124,15 @@ let ContractForm = React.createClass({
required/> required/>
</Property> </Property>
{this.getContracts()} {this.getContracts()}
<Property <PropertyCollapsible
name='appendix' name='appendix'
label={getLangText('Appendix')}> checkboxLabel={getLangText('Add appendix to the contract')}>
<input <span>{getLangText('Appendix')}</span>
type="text" <InputTextAreaToggable
placeholder={getLangText('Add an appendix to the contract')} rows={1}
required/> editable={true}
</Property> placeholder={getLangText('This will be appended to the contract selected above')}/>
</PropertyCollapsible>
</Form> </Form>
); );
} }

View File

@ -68,8 +68,12 @@ let LoanForm = React.createClass({
return this.props.id; return this.props.id;
}, },
handleOnBlur(event) { handleOnChange(event) {
LoanContractActions.fetchLoanContract(event.target.value); let potentialEmail = event.target.value;
if(potentialEmail.match(/.*@.*/)) {
LoanContractActions.fetchLoanContract(potentialEmail);
}
}, },
getContractCheckbox() { getContractCheckbox() {
@ -151,7 +155,7 @@ let LoanForm = React.createClass({
<Property <Property
name='loanee' name='loanee'
label={getLangText('Loanee Email')} label={getLangText('Loanee Email')}
onBlur={this.handleOnBlur} onChange={this.handleOnChange}
editable={!this.props.email} editable={!this.props.email}
overrideForm={!!this.props.email}> overrideForm={!!this.props.email}>
<input <input

View File

@ -20,6 +20,7 @@ import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
import HeaderNotificationDebug from './header_notification_debug'; import HeaderNotificationDebug from './header_notification_debug';
import NavRoutesLinks from './nav_routes_links';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
@ -27,7 +28,8 @@ import { getLangText } from '../utils/lang_utils';
let Header = React.createClass({ let Header = React.createClass({
propTypes: { propTypes: {
showAddWork: React.PropTypes.bool showAddWork: React.PropTypes.bool,
routes: React.PropTypes.element
}, },
mixins: [Router.State], mixins: [Router.State],
@ -89,10 +91,9 @@ let Header = React.createClass({
}, },
render() { render() {
let account = null; let account;
let signup = null; let signup;
let collection = null; let navRoutesLinks;
let addNewWork = null;
if (this.state.currentUser.username){ if (this.state.currentUser.username){
account = ( account = (
<DropdownButton eventKey="1" title={this.state.currentUser.username}> <DropdownButton eventKey="1" title={this.state.currentUser.username}>
@ -101,9 +102,7 @@ let Header = React.createClass({
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink> <MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
</DropdownButton> </DropdownButton>
); );
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} navbar right/>;
collection = <NavItemLink to="pieces" query={this.getQuery()}>{getLangText('COLLECTION')}</NavItemLink>;
addNewWork = this.props.showAddWork ? <NavItemLink to="register_piece" query={{'slide_num': 0}}>+ {getLangText('NEW WORK')}</NavItemLink> : null;
} }
else { else {
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>; account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
@ -124,11 +123,10 @@ let Header = React.createClass({
</Nav> </Nav>
<Nav navbar right> <Nav navbar right>
<HeaderNotificationDebug show={false}/> <HeaderNotificationDebug show={false}/>
{addNewWork}
{collection}
{account} {account}
{signup} {signup}
</Nav> </Nav>
{navRoutesLinks}
</CollapsibleNav> </CollapsibleNav>
</Navbar> </Navbar>
</div> </div>

View File

@ -0,0 +1,68 @@
'use strict';
import React from 'react';
import Nav from 'react-bootstrap/lib/Nav';
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
import { sanitizeList } from '../utils/general_utils';
let NavRoutesLinks = React.createClass({
propTypes: {
routes: React.PropTypes.element
},
extractLinksFromRoutes(node, i) {
if(!node) {
return;
}
node = node.props;
let links = node.children.map((child, j) => {
// check if this a candidate for a link generation
if(child.props.headerTitle && typeof child.props.headerTitle === 'string') {
// also check if it is a candidate for generating a dropdown menu
if(child.props.children && child.props.children.length > 0) {
return (
<DropdownButton title={child.props.headerTitle} key={j}>
{this.extractLinksFromRoutes(child, i++)}
</DropdownButton>
);
} else if(i === 1) {
// if the node's child is actually a node of level one (a child of a node), we're
// returning a DropdownButton matching MenuItemLink
return (
<MenuItemLink to={child.props.name} key={j}>{child.props.headerTitle}</MenuItemLink>
);
} else if(i === 0) {
return (
<NavItemLink to={child.props.name} key={j}>{child.props.headerTitle}</NavItemLink>
);
} else {
return null;
}
} else {
return null;
}
});
// remove all nulls from the list of generated links
return sanitizeList(links);
},
render() {
return (
<Nav {...this.props}>
{this.extractLinksFromRoutes(this.props.routes, 0)}
</Nav>
);
}
});
export default NavRoutesLinks;

View File

@ -69,7 +69,7 @@ let PieceList = React.createClass({
}, },
componentDidUpdate() { componentDidUpdate() {
if (this.props.redirectTo && this.state.pieceListCount === 0) { if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
// FIXME: hack to redirect out of the dispatch cycle // FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.transitionTo(this.props.redirectTo), 0); window.setTimeout(() => this.transitionTo(this.props.redirectTo), 0);
} }

View File

@ -1,25 +0,0 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import App from './ascribe_app';
import AppConstants from '../constants/application_constants';
let Route = Router.Route;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
function getRoutes(commonRoutes) {
return (
<Route name="app" path={baseUrl} handler={App}>
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
{commonRoutes}
</Route>
);
}
export default getRoutes;

View File

@ -7,6 +7,8 @@ import Header from '../../header';
import Footer from '../../footer'; import Footer from '../../footer';
import GlobalNotification from '../../global_notification'; import GlobalNotification from '../../global_notification';
import getRoutes from './prize_routes';
let RouteHandler = Router.RouteHandler; let RouteHandler = Router.RouteHandler;
let PrizeApp = React.createClass({ let PrizeApp = React.createClass({
@ -14,10 +16,14 @@ let PrizeApp = React.createClass({
render() { render() {
let header = null; let header = null;
let subdomain = window.location.host.split('.')[0];
let ROUTES = getRoutes(null, subdomain);
if (this.isActive('landing') || this.isActive('login') || this.isActive('signup')) { if (this.isActive('landing') || this.isActive('login') || this.isActive('signup')) {
header = <Hero />; header = <Hero />;
} else { } else {
header = <Header showAddWork={false} />; header = <Header showAddWork={false} routes={ROUTES}/>;
} }
return ( return (

View File

@ -29,8 +29,8 @@ function getRoutes() {
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={PrizeRegisterPiece} /> <Route name="register_piece" path="register_piece" handler={PrizeRegisterPiece} headerTitle="+ NEW WORK" />
<Route name="pieces" path="collection" handler={PrizePieceList} /> <Route name="pieces" path="collection" handler={PrizePieceList} headerTitle="COLLECTION" />
<Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} /> <Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />

View File

@ -216,6 +216,8 @@ let CylandRegisterPiece = React.createClass({
gallery="Cyland Archive" gallery="Cyland Archive"
startdate={today} startdate={today}
enddate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain} enddate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain}
showStartDate={false}
showEndDate={false}
showPersonalMessage={false} showPersonalMessage={false}
handleSuccess={this.handleLoanSuccess}> handleSuccess={this.handleLoanSuccess}>
<Property <Property

View File

@ -14,8 +14,11 @@ import GlobalNotificationModel from '../../../../../../models/global_notificatio
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button'; import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
import AclProxy from '../../../../../acl_proxy'; import AclProxy from '../../../../../acl_proxy';
import AclButton from '../../../../../ascribe_buttons/acl_button';
import { getLangText } from '../../../../../../utils/lang_utils'; import { getLangText } from '../../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../../utils/general_utils';
@ -66,9 +69,19 @@ let IkonotvAccordionListItem = React.createClass({
aclObject={this.props.content.acl} aclObject={this.props.content.acl}
aclName="acl_submit"> aclName="acl_submit">
<IkonotvSubmitButton <IkonotvSubmitButton
className="pull-right" className="btn-xs pull-right"
piece={this.props.content} handleSuccess={this.handleSubmitSuccess}
handleSuccess={this.handleSubmitSuccess}/> piece={this.props.content}/>
</AclProxy>
<AclProxy
aclObject={this.props.content.acl}
aclName="acl_submitted">
<button
disabled
className="btn btn-default btn-xs pull-right">
{getLangText('Loaned to IkonoTV')} <span className="glyphicon glyphicon-ok"
aria-hidden="true"></span>
</button>
</AclProxy> </AclProxy>
</div> </div>
); );

View File

@ -1,10 +1,18 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Moment from 'moment';
import classNames from 'classnames'; import classNames from 'classnames';
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
import LoanForm from '../../../../../ascribe_forms/form_loan';
import Property from '../../../../../ascribe_forms/property';
import InputCheckbox from '../../../../../ascribe_forms/input_checkbox';
import ApiUrls from '../../../../../../constants/api_urls';
import { getLangText } from '../../../../../../utils/lang_utils'; import { getLangText } from '../../../../../../utils/lang_utils';
let IkonotvSubmitButton = React.createClass({ let IkonotvSubmitButton = React.createClass({
@ -17,23 +25,50 @@ let IkonotvSubmitButton = React.createClass({
getSubmitButton() { getSubmitButton() {
return ( return (
<button <button
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}> className={classNames('btn', 'btn-default', this.props.className)}>
{getLangText('Loan to IkonoTV')} {getLangText('Loan to IkonoTV')}
</button> </button>
); );
}, },
render() { render() {
let today = new Moment();
let enddate = new Moment();
enddate.add(1, 'years');
return ( return (
<ModalWrapper <ModalWrapper
trigger={this.getSubmitButton()} trigger={this.getSubmitButton()}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
title={getLangText('Loan to IkonoTV')}> title={getLangText('Loan to IkonoTV archive')}>
<LoanForm
id={{piece_id: this.props.piece.id}}
url={ApiUrls.ownership_loans_pieces}
email="submissions@ikono.org"
startdate={today}
enddate={enddate}
gallery="IkonoTV archive"
showPersonalMessage={false}
handleSuccess={this.props.handleSuccess}>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox>
<span>
{' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
(<a href="https://d1qjsxua1o9x03.cloudfront.net/live/743394beff4b1282ba735e5e3723ed74/contract/bbc92f1d-4504-49f8-818c-8dd7113c6e06.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')}
</a>)
</span>
</InputCheckbox>
</Property>
</LoanForm>
</ModalWrapper> </ModalWrapper>
); );
} }
}); });
export default IkonotvSubmitButton; export default IkonotvSubmitButton;

View File

@ -0,0 +1,176 @@
'use strict';
import React from 'react';
import PieceActions from '../../../../../../actions/piece_actions';
import PieceStore from '../../../../../../stores/piece_store';
import PieceListActions from '../../../../../../actions/piece_list_actions';
import PieceListStore from '../../../../../../stores/piece_list_store';
import UserStore from '../../../../../../stores/user_store';
import Piece from '../../../../../../components/ascribe_detail/piece';
import ListRequestActions from '../../../../../ascribe_forms/list_form_request_actions';
import AclButtonList from '../../../../../ascribe_buttons/acl_button_list';
import DeleteButton from '../../../../../ascribe_buttons/delete_button';
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
import HistoryIterator from '../../../../../ascribe_detail/history_iterator';
import DetailProperty from '../../../../../ascribe_detail/detail_property';
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
import AclProxy from '../../../../../acl_proxy';
import AppConstants from '../../../../../../constants/application_constants';
import { getLangText } from '../../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../../utils/general_utils';
let IkonotvPieceContainer = React.createClass({
getInitialState() {
return mergeOptions(
PieceStore.getState(),
UserStore.getState(),
PieceListStore.getState()
);
},
componentDidMount() {
PieceStore.listen(this.onChange);
PieceActions.fetchOne(this.props.params.pieceId);
UserStore.listen(this.onChange);
PieceListStore.listen(this.onChange);
},
componentWillReceiveProps(nextProps) {
if(this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.updatePiece({});
PieceActions.fetchOne(nextProps.params.pieceId);
}
},
componentWillUnmount() {
// Every time we're leaving the piece detail page,
// just reset the piece that is saved in the piece store
// as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
PieceStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
PieceListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId);
},
handleSubmitSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.loadPiece();
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
getActions(){
if (this.state.piece &&
this.state.piece.request_action &&
this.state.piece.request_action.length > 0) {
return (
<ListRequestActions
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/>
);
}
else {
//We need to disable the normal acl_loan because we're inserting a custom acl_loan button
let availableAcls;
if(this.state.piece && this.state.piece.acl && typeof this.state.piece.acl.acl_loan !== 'undefined') {
// make a copy to not have side effects
availableAcls = mergeOptions({}, this.state.piece.acl);
availableAcls.acl_loan = false;
}
return (
<AclButtonList
className="text-center ascribe-button-list"
availableAcls={availableAcls}
editions={this.state.piece}
handleSuccess={this.loadPiece}>
<AclProxy
aclObject={availableAcls}
aclName="acl_submit">
<IkonotvSubmitButton
className="btn-sm"
handleSuccess={this.handleSubmitSuccess}
piece={this.state.piece}/>
</AclProxy>
<DeleteButton
handleSuccess={this.handleDeleteSuccess}
piece={this.state.piece}/>
</AclButtonList>
);
}
},
render() {
if('title' in this.state.piece) {
return (
<Piece
piece={this.state.piece}
loadPiece={this.loadPiece}
header={
<div className="ascribe-detail-header">
<hr style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
<DetailProperty label="BY" value={this.state.piece.artist_name} />
<DetailProperty label="DATE" value={ this.state.piece.date_created.slice(0, 4) } />
<hr/>
</div>
}
subheader={
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
<hr/>
</div>
}
buttons={this.getActions()}>
<CollapsibleParagraph
title={getLangText('Loan History')}
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
<HistoryIterator
history={this.state.piece.loan_history} />
</CollapsibleParagraph>
</Piece>
);
} else {
return (
<div className="fullpage-spinner">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>
);
}
}
});
export default IkonotvPieceContainer;

View File

@ -1,80 +0,0 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../stores/whitelabel_store';
import PieceListStore from '../../../../../stores/piece_list_store';
import PieceListActions from '../../../../../actions/piece_list_actions';
import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions';
import PieceStore from '../../../../../stores/piece_store';
import PieceActions from '../../../../../actions/piece_actions';
import ContractForm from './ascribe_forms/ikonotv_contract_form';
import RegisterPieceForm from '../../../../../components/ascribe_forms/form_register_piece';
import Property from '../../../../../components/ascribe_forms/property';
import InputCheckbox from '../../../../../components/ascribe_forms/input_checkbox';
import GlobalNotificationModel from '../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils';
let IkonotvRegisterPiece = React.createClass({
mixins: [Router.Navigation],
getInitialState(){
return mergeOptions(
UserStore.getState(),
WhitelabelStore.getState());
},
componentDidMount() {
UserStore.listen(this.onChange);
WhitelabelStore.listen(this.onChange);
UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
if (this.state.currentUser &&
this.state.whitelabel &&
this.state.whitelabel.user &&
this.state.currentUser.email === this.state.whitelabel.user){
return (
<ContractForm />
);
}
return (
<div className="ascribe-form-bordered ascribe-form-wrapper">
<RegisterPieceForm
enableLocalHashing={false}
headerMessage={getLangText('Register your work')}
submitMessage={getLangText('Register')}
handleSuccess={this.handleRegisterSuccess}/>
</div>
);
}
});
export default IkonotvRegisterPiece;

View File

@ -0,0 +1,16 @@
'use strict';
import React from 'react';
import ContractForm from '../../../../../components/ascribe_forms/contract_form';
let IkonotvRequestLoan = React.createClass({
render() {
return (
<ContractForm />
);
}
});
export default IkonotvRequestLoan;

View File

@ -7,21 +7,26 @@ import Footer from '../../footer';
import GlobalNotification from '../../global_notification'; import GlobalNotification from '../../global_notification';
import getRoutes from './wallet_routes';
let RouteHandler = Router.RouteHandler; let RouteHandler = Router.RouteHandler;
let WalletApp = React.createClass({ let WalletApp = React.createClass({
mixins: [Router.State], mixins: [Router.State],
render() { render() {
let header = null;
let subdomain = window.location.host.split('.')[0]; let subdomain = window.location.host.split('.')[0];
let ROUTES = getRoutes(null, subdomain);
let header = null;
if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup')) if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup'))
&& (['ikonotv', 'cyland']).indexOf(subdomain) > -1) { && (['ikonotv', 'cyland']).indexOf(subdomain) > -1) {
header = ( header = (
<div className="hero"/>); <div className="hero"/>);
} else { } else {
header = <Header showAddWork={true} />; header = <Header showAddWork={true} routes={ROUTES} />;
} }
return ( return (
<div className="container ascribe-prize-app"> <div className="container ascribe-prize-app">
{header} {header}

View File

@ -12,15 +12,16 @@ import PieceList from '../../../components/piece_list';
import PieceContainer from '../../../components/ascribe_detail/piece_container'; import PieceContainer from '../../../components/ascribe_detail/piece_container';
import EditionContainer from '../../../components/ascribe_detail/edition_container'; import EditionContainer from '../../../components/ascribe_detail/edition_container';
import SettingsContainer from '../../../components/settings_container'; import SettingsContainer from '../../../components/settings_container';
import RegisterPiece from '../../../components/register_piece';
// specific components
import CylandLanding from './components/cyland/cyland_landing'; import CylandLanding from './components/cyland/cyland_landing';
import CylandPieceContainer from './components/cyland/ascribe_detail/cyland_piece_container'; import CylandPieceContainer from './components/cyland/ascribe_detail/cyland_piece_container';
import CylandRegisterPiece from './components/cyland/cyland_register_piece'; import CylandRegisterPiece from './components/cyland/cyland_register_piece';
import CylandPieceList from './components/cyland/cyland_piece_list'; import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list'; import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan';
import IkonotvPieceContainer from './components/ikonotv/ascribe_detail/ikonotv_piece_container';
import CCRegisterPiece from './components/cc/cc_register_piece'; import CCRegisterPiece from './components/cc/cc_register_piece';
@ -40,8 +41,8 @@ let ROUTES = {
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={CylandRegisterPiece} /> <Route name="register_piece" path="register_piece" handler={CylandRegisterPiece} headerTitle="+ NEW WORK" />
<Route name="pieces" path="collection" handler={CylandPieceList} /> <Route name="pieces" path="collection" handler={CylandPieceList} headerTitle="COLLECTION" />
<Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} /> <Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
@ -55,8 +56,8 @@ let ROUTES = {
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={CCRegisterPiece} /> <Route name="register_piece" path="register_piece" handler={CCRegisterPiece} headerTitle="+ NEW WORK" />
<Route name="pieces" path="collection" handler={PieceList} /> <Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" />
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} /> <Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
@ -64,14 +65,16 @@ let ROUTES = {
), ),
'ikonotv': ( 'ikonotv': (
<Route name="app" path={baseUrl} handler={WalletApp}> <Route name="app" path={baseUrl} handler={WalletApp}>
<Route name="landing" path={baseUrl} handler={IkonotvRegisterPiece} /> <Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
<Route name="login" path="login" handler={LoginContainer} /> <Route name="login" path="login" handler={LoginContainer} />
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={IkonotvRegisterPiece} /> <Route name="request_loan" path="request_loan" handler={IkonotvRequestLoan}/>
<Route name="pieces" path="collection" handler={IkonotvPieceList} /> <Route name="register_piece" path="register_piece" handler={RegisterPiece} headerTitle="+ NEW WORK"/>
<Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} /> <Route name="pieces" path="collection" handler={IkonotvPieceList} headerTitle="COLLECTION"/>
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
</Route> </Route>

View File

@ -5,7 +5,8 @@ import Router from 'react-router';
import getPrizeRoutes from './components/whitelabel/prize/prize_routes'; import getPrizeRoutes from './components/whitelabel/prize/prize_routes';
import getWalletRoutes from './components/whitelabel/wallet/wallet_routes'; import getWalletRoutes from './components/whitelabel/wallet/wallet_routes';
import getDefaultRoutes from './components/routes';
import App from './components/ascribe_app';
import PieceList from './components/piece_list'; import PieceList from './components/piece_list';
import PieceContainer from './components/ascribe_detail/piece_container'; import PieceContainer from './components/ascribe_detail/piece_container';
@ -23,19 +24,25 @@ import RegisterPiece from './components/register_piece';
import PrizesDashboard from './components/ascribe_prizes_dashboard/prizes_dashboard'; import PrizesDashboard from './components/ascribe_prizes_dashboard/prizes_dashboard';
import AppConstants from './constants/application_constants';
let Route = Router.Route; let Route = Router.Route;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
const COMMON_ROUTES = ( const COMMON_ROUTES = (
<Route> <Route name="app" path={baseUrl} handler={App}>
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
<Route name="signup" path="signup" handler={SignupContainer} /> <Route name="signup" path="signup" handler={SignupContainer} />
<Route name="login" path="login" handler={LoginContainer} /> <Route name="login" path="login" handler={LoginContainer} />
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route name="logout" path="logout" handler={LogoutContainer} />
<Route name="pieces" path="collection" handler={PieceList} /> <Route name="register_piece" path="register_piece" handler={RegisterPiece} headerTitle="+ NEW WORK" />
<Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" />
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} /> <Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Route name="prizes" path="prizes" handler={PrizesDashboard} /> <Route name="prizes" path="prizes" handler={PrizesDashboard} />
@ -51,7 +58,7 @@ function getRoutes(type, subdomain) {
} else if(type === 'wallet') { } else if(type === 'wallet') {
routes = getWalletRoutes(COMMON_ROUTES, subdomain); routes = getWalletRoutes(COMMON_ROUTES, subdomain);
} else { } else {
routes = getDefaultRoutes(COMMON_ROUTES); routes = COMMON_ROUTES;
} }
return routes; return routes;

View File

@ -21,6 +21,7 @@ class PieceListStore {
this.pieceList = []; this.pieceList = [];
// -1 specifies that the store is currently loading // -1 specifies that the store is currently loading
this.pieceListCount = -1; this.pieceListCount = -1;
this.unfilteredPieceListCount = -1;
this.page = 1; this.page = 1;
this.pageSize = 10; this.pageSize = 10;
this.search = ''; this.search = '';
@ -30,13 +31,14 @@ class PieceListStore {
this.bindActions(PieceListActions); this.bindActions(PieceListActions);
} }
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount, filterBy }) { onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount, unfilteredPieceListCount, filterBy }) {
this.page = page; this.page = page;
this.pageSize = pageSize; this.pageSize = pageSize;
this.search = search; this.search = search;
this.orderAsc = orderAsc; this.orderAsc = orderAsc;
this.orderBy = orderBy; this.orderBy = orderBy;
this.pieceListCount = pieceListCount; this.pieceListCount = pieceListCount;
this.unfilteredPieceListCount = unfilteredPieceListCount;
this.filterBy = filterBy; this.filterBy = filterBy;
/** /**

View File

@ -26,6 +26,23 @@ export function sanitize(obj, filterFn) {
return obj; return obj;
} }
/**
* Removes all falsy values (undefined, null, false, ...) from a list/array
* @param {array} l the array to sanitize
* @return {array} the sanitized array
*/
export function sanitizeList(l) {
let sanitizedList = [];
for(let i = 0; i < l.length; i++) {
if(l[i]) {
sanitizedList.push(l[i]);
}
}
return sanitizedList;
}
/** /**
* Sums up a list of numbers. Like a Epsilon-math-kinda-sum... * Sums up a list of numbers. Like a Epsilon-math-kinda-sum...
*/ */

View File

@ -69,6 +69,7 @@
"lodash": "^3.9.3", "lodash": "^3.9.3",
"moment": "^2.10.6", "moment": "^2.10.6",
"object-assign": "^2.0.0", "object-assign": "^2.0.0",
"opn": "^3.0.2",
"q": "^1.4.1", "q": "^1.4.1",
"raven-js": "^1.1.19", "raven-js": "^1.1.19",
"react": "^0.13.2", "react": "^0.13.2",

View File

@ -115,9 +115,7 @@ hr {
.img-brand { .img-brand {
height: 45px; height: 60px;
margin: 5px 0;
padding: 0;
} }
.truncate { .truncate {
@ -381,7 +379,7 @@ hr {
margin-top: 1px; margin-top: 1px;
} }
.margin-left-2px{ .margin-left-2px {
margin-left: 2px; margin-left: 2px;
} }