cyland refactor

whitelabel + cyland app
This commit is contained in:
diminator 2015-08-17 20:52:36 +02:00
parent 0be3e249bb
commit fc82c866fb
18 changed files with 381 additions and 60 deletions

View File

@ -13,6 +13,7 @@ let Link = Router.Link;
let AccordionListItemPiece = React.createClass({
propTypes: {
className: React.PropTypes.string,
artistName: React.PropTypes.string,
piece: React.PropTypes.object,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
@ -26,24 +27,12 @@ let AccordionListItemPiece = React.createClass({
mixins: [Router.Navigation],
getLinkData(){
let linkData;
if (this.props.piece.num_editions < 1 || !this.props.piece.first_edition) {
linkData = {
to: 'piece',
params: {
pieceId: this.props.piece.id
}
};
} else {
linkData = {
to: 'edition',
params: {
editionId: this.props.piece.first_edition.bitcoin_id
}
};
}
return linkData;
return {
to: 'piece',
params: {
pieceId: this.props.piece.id
}
};
},
render() {

View File

@ -20,7 +20,7 @@ let FurtherDetails = React.createClass({
editable: React.PropTypes.bool,
pieceId: React.PropTypes.number,
extraData: React.PropTypes.object,
otherData: React.PropTypes.object,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
handleSuccess: React.PropTypes.func
},
@ -91,7 +91,8 @@ let FurtherDetails = React.createClass({
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.props.editable}
pieceId={this.props.pieceId}
otherData={this.props.otherData}/>
otherData={this.props.otherData}
multiple={true}/>
</Form>
</Col>
</Row>

View File

@ -14,7 +14,7 @@ import { getCookie } from '../../utils/fetch_api_utils';
let FurtherDetailsFileuploader = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
otherData: React.PropTypes.object,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
@ -37,6 +37,8 @@ let FurtherDetailsFileuploader = React.createClass({
if (!this.props.editable && !this.props.otherData){
return null;
}
let otherDataIds = this.props.otherData ? this.props.otherData.map((data)=>{return data.id; }).join() : null;
return (
<Property
label="Additional files (max. 10MB)">
@ -63,7 +65,7 @@ let FurtherDetailsFileuploader = React.createClass({
'X-CSRFToken': getCookie(AppConstants.csrftoken)
},
params: {
'pk': this.props.otherData ? this.props.otherData.id : null
'pk': otherDataIds
},
cors: {
expected: true,

View File

@ -148,7 +148,7 @@ let PieceContainer = React.createClass({
loadPiece={this.loadPiece}
header={
<div className="ascribe-detail-header">
<hr/>
<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) } />

View File

@ -98,7 +98,7 @@ let AccordionListItemPrize = React.createClass({
<div>
<AclProxy
aclObject={this.props.content.acl}
aclName="acl_submit_to_prize">
aclName="acl_submit">
<SubmitToPrizeButton
className="pull-right"
piece={this.props.content}

View File

@ -4,11 +4,11 @@ import AppPrizeConstants from './prize_application_constants';
function getPrizeApiUrls(subdomain) {
return {
'pieces_list': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/',
'users_login': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/login/',
'users_signup': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/',
'user': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/',
'piece_submit_to_prize': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/${piece_id}/submit/',
'pieces_list': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/',
'piece': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/${piece_id}/',
'prize': AppPrizeConstants.prizeApiEndpoint + subdomain + '/',
'jurys': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/',

View File

@ -21,7 +21,7 @@ let PrizeApp = React.createClass({
}
return (
<div className="ascribe-prize-app">
<div className="container ascribe-prize-app">
{header}
<div className="wp">
<RouteHandler />

View File

@ -0,0 +1,93 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece';
import PieceListActions from '../../../../../../actions/piece_list_actions';
import PieceListStore from '../../../../../../stores/piece_list_store';
import UserStore from '../../../../../../stores/user_store';
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
import CylandSubmitButton from '../ascribe_buttons/cyland_submit_button';
import AclProxy from '../../../../../acl_proxy';
import { getLangText } from '../../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../../utils/general_utils';
let CylandAccordionListItem = React.createClass({
propTypes: {
className: React.PropTypes.string,
content: React.PropTypes.object,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
},
getInitialState() {
return mergeOptions(
PieceListStore.getState(),
UserStore.getState()
);
},
componentDidMount() {
PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange);
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
handleSubmitSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
getSubmitButtons() {
return (
<div>
<AclProxy
aclObject={this.props.content.acl}
aclName="acl_submit">
<CylandSubmitButton
className="pull-right"
piece={this.props.content}
handleSuccess={this.handleSubmitSuccess}/>
</AclProxy>
</div>
);
},
render() {
return (
<AccordionListItemPiece
className={this.props.className}
piece={this.props.content}
subsubheading={
<div className="pull-left">
<span>{this.props.content.date_created.split('-')[0]}</span>
</div>}
buttons={this.getSubmitButtons()}>
{this.props.children}
</AccordionListItemPiece>
);
}
});
export default CylandAccordionListItem;

View File

@ -0,0 +1,54 @@
'use strict';
import React from 'react';
import classNames from 'classnames';
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
import PieceSubmitToPrizeForm from '../../../../../ascribe_forms/form_submit_to_prize';
import { getLangText } from '../../../../../../utils/lang_utils';
let SubmitToPrizeButton = React.createClass({
propTypes: {
className: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired
},
getSubmitButton() {
if (this.props.piece.prize) {
return (
<button
disabled
className="btn btn-default btn-xs pull-right">
{getLangText('Submitted to prize')} <span className="glyphicon glyphicon-ok"
aria-hidden="true"></span>
</button>
);
}
else {
return (
<button
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
{getLangText('Submit to Cyland')}
</button>
);
}
},
render() {
return (
<ModalWrapper
trigger={this.getSubmitButton()}
handleSuccess={this.props.handleSuccess}
title={getLangText('Submit to prize')}>
<PieceSubmitToPrizeForm
piece={this.props.piece}
handleSuccess={this.props.handleSuccess}/>
</ModalWrapper>
);
}
});
export default SubmitToPrizeButton;

View File

@ -0,0 +1,137 @@
'use strict';
import React from 'react';
import PieceActions from '../../../../../../actions/piece_actions';
import PieceStore from '../../../../../../stores/piece_store';
import UserStore from '../../../../../../stores/user_store';
import Piece from '../../../../../../components/ascribe_detail/piece';
import AppConstants from '../../../../../../constants/application_constants';
import Form from '../../../../../../components/ascribe_forms/form';
import Property from '../../../../../../components/ascribe_forms/property';
import InputTextAreaToggable from '../../../../../../components/ascribe_forms/input_textarea_toggable';
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import DetailProperty from '../../../../../ascribe_detail/detail_property';
import { mergeOptions } from '../../../../../../utils/general_utils';
import { getLangText } from '../../../../../../utils/lang_utils';
/**
* This is the component that implements resource/data specific functionality
*/
let CylandPieceContainer = React.createClass({
getInitialState() {
return mergeOptions(
PieceStore.getState(),
UserStore.getState()
);
},
componentDidMount() {
PieceStore.listen(this.onChange);
PieceActions.fetchOne(this.props.params.pieceId);
UserStore.listen(this.onChange);
},
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);
},
componentWillReceiveProps(nextProps) {
if(this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.updatePiece({});
PieceActions.fetchOne(nextProps.params.pieceId);
}
},
onChange(state) {
this.setState(state);
},
loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId);
},
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>
}>
<CylandPieceDetails piece={this.state.piece}/>
</Piece>
);
} else {
return (
<div className="fullpage-spinner">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>
);
}
}
});
let CylandPieceDetails = React.createClass({
propTypes: {
piece: React.PropTypes.object
},
render() {
if (Object.keys(this.props.piece.extra_data).length !== 0){
return (
<CollapsibleParagraph
title="Further Details"
show={true}
defaultExpanded={true}>
<Form ref='form'>
{Object.keys(this.props.piece.extra_data).map((data) => {
let label = data.replace('_', ' ');
return (
<Property
name={data}
label={label}
editable={false}>
<InputTextAreaToggable
rows={1}
editable={false}
defaultValue={this.props.piece.extra_data[data]}/>
</Property>);
}
)}
<FurtherDetailsFileuploader
editable={false}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
multiple={false}/>
<hr />
</Form>
</CollapsibleParagraph>
);
}
return null;
}
});
export default CylandPieceContainer;

View File

@ -2,19 +2,19 @@
import React from 'react';
import Form from '../../../../ascribe_forms/form';
import Property from '../../../../ascribe_forms/property';
import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../../ascribe_forms/input_textarea_toggable';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import FurtherDetailsFileuploader from '../../../../ascribe_detail/further_details_fileuploader';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import ApiUrls from '../../../../../constants/api_urls';
import AppConstants from '../../../../../constants/application_constants';
import ApiUrls from '../../../../../../constants/api_urls';
import AppConstants from '../../../../../../constants/application_constants';
import requests from '../../../../../utils/requests';
import requests from '../../../../../../utils/requests';
import { getLangText } from '../../../../../utils/lang_utils';
import { getLangText } from '../../../../../../utils/lang_utils';
let CylandAdditionalDataForm = React.createClass({
propTypes: {
@ -107,7 +107,7 @@ let CylandAdditionalDataForm = React.createClass({
editable={true}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
multiple={false}/>
multiple={true}/>
</Form>
);
} else {

View File

@ -0,0 +1,42 @@
'use strict';
import React from 'react';
import PieceList from '../../../../piece_list';
import UserActions from '../../../../../actions/user_actions';
import UserStore from '../../../../../stores/user_store';
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
let CylandPieceList = React.createClass({
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
return (
<div>
<PieceList
redirectTo="register_piece"
accordionListItemType={CylandAccordionListItem}
/>
</div>
);
}
});
export default CylandPieceList;

View File

@ -13,6 +13,9 @@ import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import Property from '../../../../ascribe_forms/property';
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
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';
@ -25,7 +28,7 @@ import PieceActions from '../../../../../actions/piece_actions';
import GlobalNotificationModel from '../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
import CylandAdditionalDataForm from '../ascribe_forms/cyland_additional_data_form';
import CylandAdditionalDataForm from './ascribe_forms/cyland_additional_data_form';
import LoanForm from '../../../../ascribe_forms/form_loan';
@ -46,6 +49,7 @@ let CylandRegisterPiece = React.createClass({
UserStore.getState(),
PieceListStore.getState(),
PieceStore.getState(),
WhitelabelStore.getState(),
{
selectedLicense: 0,
isFineUploaderActive: false
@ -56,14 +60,16 @@ let CylandRegisterPiece = React.createClass({
PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange);
PieceStore.listen(this.onChange);
WhitelabelStore.listen(this.onChange);
UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel();
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
PieceStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
},
onChange(state) {
@ -105,7 +111,7 @@ let CylandRegisterPiece = React.createClass({
handleLoanSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
PieceActions.fetchOne(this.state.piece.id);
this.transitionTo('piece', {pieceId: this.state.piece.id});
},
@ -177,7 +183,7 @@ let CylandRegisterPiece = React.createClass({
message={getAclFormMessage('acl_loan', '\"' + this.state.piece.title + '\"', this.state.currentUser.username)}
id={{piece_id: this.state.piece.id}}
url={ApiUrls.ownership_loans_pieces}
email="videoarchive@mailinator.com"
email={this.state.whitelabel.user}
gallery="Cyland Archive"
startdate={today}
enddate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain}

View File

@ -1,8 +1,16 @@
'use strict';
import walletConstants from './wallet_application_constants';
// gets subdomain as a parameter
function getPrizeApiUrls() {
function getWalletApiUrls(subdomain) {
if (subdomain === 'cyland'){
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/'
};
}
return {};
}
export default getPrizeApiUrls;
export default getWalletApiUrls;

View File

@ -3,7 +3,7 @@
import AppConstants from '../../../../constants/application_constants';
let walletConstants = {
walletApiEndpoint: AppConstants.apiEndpoint + 'wallets/'
walletApiEndpoint: AppConstants.apiEndpoint + 'whitelabel/'
};
export default walletConstants;

View File

@ -10,7 +10,7 @@ let RouteHandler = Router.RouteHandler;
let WalletApp = React.createClass({
render() {
return (
<div className="ascribe-prize-app">
<div className="container ascribe-prize-app">
<Header />
<RouteHandler />
<GlobalNotification />

View File

@ -14,7 +14,10 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain
import SettingsContainer from '../../../components/settings_container';
// specific components
import CylandPieceContainer from './components/cyland/ascribe_detail/cyland_piece_container';
import CylandRegisterPiece from './components/cyland/cyland_register_piece';
import CylandPieceList from './components/cyland/cyland_piece_list';
import CCRegisterPiece from './components/cc/cc_register_piece';
import WalletApp from './wallet_app';
@ -34,8 +37,8 @@ let ROUTES = {
<Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={CylandRegisterPiece} />
<Route name="pieces" path="collection" handler={PieceList} />
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
<Route name="pieces" path="collection" handler={CylandPieceList} />
<Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} />
</Route>

View File

@ -12,7 +12,7 @@ let constants = {
'baseUrl': window.BASE_URL,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions',
'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer', 'acl_submit_to_prize'],
'acl_withdraw_transfer', 'acl_submit'],
'version': 0.1,
'csrftoken': 'csrftoken2',
@ -25,13 +25,6 @@ let constants = {
'type': 'wallet',
'ga': 'UA-60614729-4'
},
{
'subdomain': 'cc-staging',
'name': 'Creative Commons France',
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/public/creativecommons/cc.logo.sm.png',
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
'type': 'wallet'
},
{
'subdomain': 'sluice',
'name': 'Sluice Art Fair',
@ -40,13 +33,6 @@ let constants = {
'type': 'prize',
'ga': 'UA-60614729-5'
},
{
'subdomain': 'sluice-staging',
'name': 'Sluice Art Fair',
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/sluice/logo.jpeg',
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
'type': 'prize'
},
{
'subdomain': 'cyland',
'name': 'Cyland media art lab',