1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-23 01:39:36 +01:00

Merge remote-tracking branch 'remotes/origin/AD-835-make-form-fields-lockable' into AD-613-cyland-white-label-page

This commit is contained in:
diminator 2015-08-20 11:32:27 +02:00
commit 7afb3d68b8
10 changed files with 204 additions and 47 deletions

View File

@ -33,6 +33,9 @@ let Form = React.createClass({
React.PropTypes.arrayOf(React.PropTypes.element) React.PropTypes.arrayOf(React.PropTypes.element)
]), ]),
// Can be used to freeze the whole form
disabled: React.PropTypes.bool,
// You can use the form for inline requests, like the submit click on a button. // You can use the form for inline requests, like the submit click on a button.
// For the form to then not display the error on top, you need to enable this option. // For the form to then not display the error on top, you need to enable this option.
// It will make use of the GlobalNotification // It will make use of the GlobalNotification
@ -203,7 +206,11 @@ let Form = React.createClass({
if (child) { if (child) {
return ReactAddons.addons.cloneWithProps(child, { return ReactAddons.addons.cloneWithProps(child, {
handleChange: this.handleChangeChild, handleChange: this.handleChangeChild,
ref: child.props.name ref: child.props.name,
// We need this in order to make editable be overridable when setting it directly
// on Property
editable: child.props.overrideForm ? child.props.editable : !this.props.disabled
}); });
} }
}); });

View File

@ -147,7 +147,8 @@ let LoanForm = React.createClass({
name='loanee' name='loanee'
label={getLangText('Loanee Email')} label={getLangText('Loanee Email')}
onBlur={this.handleOnBlur} onBlur={this.handleOnBlur}
editable={!this.props.email}> editable={!this.props.email}
overrideForm={!this.props.email}>
<input <input
value={this.props.email} value={this.props.email}
type="email" type="email"
@ -157,7 +158,8 @@ let LoanForm = React.createClass({
<Property <Property
name='gallery_name' name='gallery_name'
label={getLangText('Gallery/exhibition (optional)')} label={getLangText('Gallery/exhibition (optional)')}
editable={!this.props.gallery}> editable={!this.props.gallery}
overrideForm={!this.props.gallery}>
<input <input
value={this.props.gallery} value={this.props.gallery}
type="text" type="text"

View File

@ -28,7 +28,10 @@ let RegisterPieceForm = React.createClass({
isFineUploaderEditable: React.PropTypes.bool, isFineUploaderEditable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
children: React.PropTypes.element, children: React.PropTypes.element,
onLoggedOut: React.PropTypes.func onLoggedOut: React.PropTypes.func,
// For this form to work with SlideContainer, we sometimes have to disable it
disabled: React.PropTypes.bool
}, },
getDefaultProps() { getDefaultProps() {
@ -84,8 +87,10 @@ let RegisterPieceForm = React.createClass({
let currentUser = this.state.currentUser; let currentUser = this.state.currentUser;
let enableLocalHashing = currentUser && currentUser.profile ? currentUser.profile.hash_locally : false; let enableLocalHashing = currentUser && currentUser.profile ? currentUser.profile.hash_locally : false;
enableLocalHashing = enableLocalHashing && this.props.enableLocalHashing; enableLocalHashing = enableLocalHashing && this.props.enableLocalHashing;
return ( return (
<Form <Form
disabled={this.props.disabled}
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={ApiUrls.pieces_list} url={ApiUrls.pieces_list}
@ -94,7 +99,7 @@ let RegisterPieceForm = React.createClass({
buttons={<button buttons={<button
type="submit" type="submit"
className="btn ascribe-btn ascribe-btn-login" className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}> disabled={!this.state.isUploadReady || this.props.disabled}>
{this.props.submitMessage} {this.props.submitMessage}
</button>} </button>}
spinner={ spinner={
@ -160,10 +165,23 @@ let FileUploader = React.createClass({
isFineUploaderActive: React.PropTypes.bool, isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func, onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
}, },
render() { render() {
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
// isFineUploaderActive
if(typeof this.props.disabled !== 'undefined') {
editable = !this.props.disabled;
}
return ( return (
<ReactS3FineUploader <ReactS3FineUploader
onClick={this.props.onClick} onClick={this.props.onClick}
@ -182,7 +200,7 @@ let FileUploader = React.createClass({
setIsUploadReady={this.props.setIsUploadReady} setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission} isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false} areAssetsDownloadable={false}
areAssetsEditable={this.props.isFineUploaderActive} areAssetsEditable={editable}
signature={{ signature={{
endpoint: AppConstants.serverUrl + 's3/signature/', endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: { customHeaders: {

View File

@ -21,7 +21,11 @@ let InputCheckbox = React.createClass({
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element React.PropTypes.element
]) ]),
// provided by Property
disabled: React.PropTypes.bool,
onChange: React.PropTypes.func
}, },
// As HTML inputs, we're setting the default value for an input to checked === false // As HTML inputs, we're setting the default value for an input to checked === false
@ -56,6 +60,12 @@ let InputCheckbox = React.createClass({
}, },
onChange() { onChange() {
// if this.props.disabled is true, we're just going to ignore every click,
// as the value should then not be changable by the user
if(this.props.disabled) {
return;
}
// On every change, we're inversing the input's value // On every change, we're inversing the input's value
let inverseValue = !this.refs.checkbox.getDOMNode().checked; let inverseValue = !this.refs.checkbox.getDOMNode().checked;
@ -74,6 +84,18 @@ let InputCheckbox = React.createClass({
}, },
render() { render() {
let style = {};
// Some conditional styling if the component is disabled
if(!this.props.disabled) {
style.cursor = 'pointer';
style.color = 'rgba(0, 0, 0, 0.5)';
} else {
style.cursor = 'not-allowed';
style.color = 'rgba(0, 0, 0, 0.35)';
}
return ( return (
<span <span
onClick={this.onChange}> onClick={this.onChange}>
@ -83,7 +105,9 @@ let InputCheckbox = React.createClass({
onChange={this.onChange} onChange={this.onChange}
checked={this.state.value} checked={this.state.value}
defaultChecked={this.props.defaultChecked}/> defaultChecked={this.props.defaultChecked}/>
<span className="checkbox"> <span
className="checkbox"
style={style}>
{this.props.children} {this.props.children}
</span> </span>
</span> </span>

View File

@ -6,10 +6,19 @@ import ReactAddons from 'react/addons';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import Tooltip from 'react-bootstrap/lib/Tooltip';
import { mergeOptions } from '../../utils/general_utils';
let Property = React.createClass({ let Property = React.createClass({
propTypes: { propTypes: {
hidden: React.PropTypes.bool, hidden: React.PropTypes.bool,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
// If we want Form to have a different value for disabled as Property has one for
// editable, we need to set overrideForm to true, as it will then override Form's
// disabled value for individual Properties
overrideForm: React.PropTypes.bool,
tooltip: React.PropTypes.element, tooltip: React.PropTypes.element,
label: React.PropTypes.string, label: React.PropTypes.string,
value: React.PropTypes.oneOfType([ value: React.PropTypes.oneOfType([
@ -167,9 +176,10 @@ let Property = React.createClass({
} }
}, },
renderChildren() { renderChildren(style) {
return ReactAddons.Children.map(this.props.children, (child) => { return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, { return ReactAddons.addons.cloneWithProps(child, {
style,
onChange: this.handleChange, onChange: this.handleChange,
onFocus: this.handleFocus, onFocus: this.handleFocus,
onBlur: this.handleBlur, onBlur: this.handleBlur,
@ -181,25 +191,32 @@ let Property = React.createClass({
render() { render() {
let tooltip = <span/>; let tooltip = <span/>;
if (this.props.tooltip){ let style = this.props.style ? mergeOptions({}, this.props.style) : {};
if(this.props.tooltip){
tooltip = ( tooltip = (
<Tooltip> <Tooltip>
{this.props.tooltip} {this.props.tooltip}
</Tooltip>); </Tooltip>);
} }
let footer = null; let footer = null;
if (this.props.footer){ if(this.props.footer){
footer = ( footer = (
<div className="ascribe-property-footer"> <div className="ascribe-property-footer">
{this.props.footer} {this.props.footer}
</div>); </div>);
} }
if(!this.props.editable) {
style.cursor = 'not-allowed';
}
return ( return (
<div <div
className={'ascribe-settings-wrapper ' + this.getClassName()} className={'ascribe-settings-wrapper ' + this.getClassName()}
onClick={this.handleFocus} onClick={this.handleFocus}
onFocus={this.handleFocus} onFocus={this.handleFocus}
style={this.props.style}> style={style}>
<OverlayTrigger <OverlayTrigger
delay={500} delay={500}
placement="top" placement="top"
@ -207,7 +224,7 @@ let Property = React.createClass({
<div className={'ascribe-settings-property ' + this.props.className}> <div className={'ascribe-settings-property ' + this.props.className}>
{this.state.errors} {this.state.errors}
<span>{ this.props.label}</span> <span>{ this.props.label}</span>
{this.renderChildren()} {this.renderChildren(style)}
{footer} {footer}
</div> </div>
</OverlayTrigger> </OverlayTrigger>

View File

@ -6,13 +6,20 @@ import ReactAddons from 'react/addons';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
let State = Router.State; let State = Router.State;
let Navigation = Router.Navigation; let Navigation = Router.Navigation;
let SlidesContainer = React.createClass({ let SlidesContainer = React.createClass({
propTypes: { propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element), children: React.PropTypes.arrayOf(React.PropTypes.element),
forwardProcess: React.PropTypes.bool.isRequired forwardProcess: React.PropTypes.bool.isRequired,
glyphiconClassNames: React.PropTypes.shape({
pending: React.PropTypes.string,
complete: React.PropTypes.string
})
}, },
mixins: [State, Navigation], mixins: [State, Navigation],
@ -192,32 +199,13 @@ let SlidesContainer = React.createClass({
// otherwise do not display the breadcrumbs at all // otherwise do not display the breadcrumbs at all
// Also, if there is only one child, do not display the breadcrumbs // Also, if there is only one child, do not display the breadcrumbs
if(breadcrumbs.length === numOfChildren && breadcrumbs.length > 1 && numOfChildren > 1) { if(breadcrumbs.length === numOfChildren && breadcrumbs.length > 1 && numOfChildren > 1) {
let numSlides = breadcrumbs.length;
let columnWidth = Math.floor(12 / numSlides);
return ( return (
<div className="row" style={{width: this.state.containerWidth}}> <SlidesContainerBreadcrumbs
<div className="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12"> breadcrumbs={breadcrumbs}
<div className="no-margin row ascribe-breadcrumb-container"> slideNum={this.state.slideNum}
{breadcrumbs.map((breadcrumb, i) => { numOfSlides={breadcrumbs.length}
return ( containerWidth={this.state.containerWidth}
<Col glyphiconClassNames={this.props.glyphiconClassNames}/>
className="no-padding"
sm={columnWidth}
key={i}>
<div className="ascribe-breadcrumb">
<a className={this.state.slideNum === i ? 'active' : ''}>
{breadcrumb}
<span className={i === numSlides - 1 ? 'invisible' : '' + 'pull-right glyphicon glyphicon-chevron-right'}>
</span>
</a>
</div>
</Col>
);
})}
</div>
</div>
</div>
); );
} else { } else {
return null; return null;
@ -228,9 +216,9 @@ let SlidesContainer = React.createClass({
// Also, a key is nice to have! // Also, a key is nice to have!
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child, i) => { return ReactAddons.Children.map(this.props.children, (child, i) => {
// since the default parameter of startFrom is -1, we do not need to check // since the default parameter of startFrom is -1, we do not need to check
// if its actually present in the url bar, as it will just not match // if its actually present in the url bar, as it will just not match
if(i >= this.state.startFrom) { if(i >= this.state.startFrom) {
return ReactAddons.addons.cloneWithProps(child, { return ReactAddons.addons.cloneWithProps(child, {
className: 'ascribe-slide', className: 'ascribe-slide',
@ -243,6 +231,7 @@ let SlidesContainer = React.createClass({
// Abortions are bad mkay // Abortions are bad mkay
return null; return null;
} }
}); });
}, },

View File

@ -0,0 +1,78 @@
'use strict';
import React from 'react';
import Col from 'react-bootstrap/lib/Col';
// Note:
//
// If we ever need generic breadcrumbs component, we should refactor this
let SlidesContainerBreadcrumbs = React.createClass({
propTypes: {
breadcrumbs: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
slideNum: React.PropTypes.number.isRequired,
numOfSlides: React.PropTypes.number.isRequired,
containerWidth: React.PropTypes.number.isRequired,
glyphiconClassNames: React.PropTypes.shape({
pending: React.PropTypes.string,
complete: React.PropTypes.string
})
},
getDefaultProps() {
return {
glyphiconClassNames: {
pending: 'glyphicon glyphicon-chevron-right',
complete: 'glyphicon glyphicon-lock'
}
};
},
render() {
let breadcrumbs = this.props.breadcrumbs;
let numSlides = breadcrumbs.length;
let columnWidth = Math.floor(12 / numSlides);
return (
<div className="row" style={{width: this.props.containerWidth}}>
<div className="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
<div className="no-margin row ascribe-breadcrumb-container">
{breadcrumbs.map((breadcrumb, i) => {
// Depending on the progress the user has already made, we display different
// glyphicons that can also be specified from the outside
let glyphiconClassName;
if(i >= this.props.slideNum) {
glyphiconClassName = this.props.glyphiconClassNames.pending;
} else {
glyphiconClassName = this.props.glyphiconClassNames.completed;
}
return (
<Col
className="no-padding"
sm={columnWidth}
key={i}>
<div className="ascribe-breadcrumb">
<a className={this.props.slideNum === i ? 'active' : ''}>
{breadcrumb}
<span className={i === numSlides - 1 ? 'invisible' : '' + 'pull-right ' + glyphiconClassName}>
</span>
</a>
</div>
</Col>
);
})}
</div>
</div>
</div>
);
}
});
export default SlidesContainerBreadcrumbs;

View File

@ -19,7 +19,9 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let CylandAdditionalDataForm = React.createClass({ let CylandAdditionalDataForm = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
piece: React.PropTypes.object.isRequired piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool
}, },
getInitialState() { getInitialState() {
@ -67,6 +69,7 @@ let CylandAdditionalDataForm = React.createClass({
if(this.props.piece && this.props.piece.id) { if(this.props.piece && this.props.piece.id) {
return ( return (
<Form <Form
disabled={this.props.disabled}
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})} url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
@ -76,7 +79,7 @@ let CylandAdditionalDataForm = React.createClass({
<button <button
type="submit" type="submit"
className="btn ascribe-btn ascribe-btn-login" className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}> disabled={!this.state.isUploadReady || this.props.disabled}>
{getLangText('Proceed to loan')} {getLangText('Proceed to loan')}
</button> </button>
} }

View File

@ -53,7 +53,8 @@ let CylandRegisterPiece = React.createClass({
WhitelabelStore.getState(), WhitelabelStore.getState(),
{ {
selectedLicense: 0, selectedLicense: 0,
isFineUploaderActive: false isFineUploaderActive: false,
step: 0
}); });
}, },
@ -99,11 +100,16 @@ let CylandRegisterPiece = React.createClass({
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
this.incrementStep();
this.refs.slidesContainer.nextSlide(); this.refs.slidesContainer.nextSlide();
}, },
handleAdditionalDataSuccess() { handleAdditionalDataSuccess() {
this.refreshPieceList(); this.refreshPieceList();
this.incrementStep();
this.refs.slidesContainer.nextSlide(); this.refs.slidesContainer.nextSlide();
}, },
@ -117,6 +123,15 @@ let CylandRegisterPiece = React.createClass({
this.transitionTo('piece', {pieceId: this.state.piece.id}); this.transitionTo('piece', {pieceId: this.state.piece.id});
}, },
// We need to increase the step to lock the forms that are already filed out
incrementStep() {
// also increase step
let newStep = this.state.step + 1;
this.setState({
step: newStep
});
},
refreshPieceList() { refreshPieceList() {
PieceListActions.fetchPieceList( PieceListActions.fetchPieceList(
this.state.page, this.state.page,
@ -150,11 +165,16 @@ let CylandRegisterPiece = React.createClass({
return ( return (
<SlidesContainer <SlidesContainer
ref="slidesContainer" ref="slidesContainer"
forwardProcess={true}> forwardProcess={true}
glyphiconClassNames={{
pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock'
}}>
<div data-slide-title="Register work"> <div data-slide-title="Register work">
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<RegisterPieceForm <RegisterPieceForm
disabled={this.state.step > 0}
enableLocalHashing={false} enableLocalHashing={false}
headerMessage={getLangText('Submit to Cyland Archive')} headerMessage={getLangText('Submit to Cyland Archive')}
submitMessage={getLangText('Submit')} submitMessage={getLangText('Submit')}
@ -182,6 +202,7 @@ let CylandRegisterPiece = React.createClass({
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<CylandAdditionalDataForm <CylandAdditionalDataForm
disabled={this.state.step > 1}
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/> piece={this.state.piece}/>
</Col> </Col>

View File

@ -153,12 +153,10 @@
/* Taken from: http://www.htmllion.com/css3-checkbox.html */ /* Taken from: http://www.htmllion.com/css3-checkbox.html */
.checkbox { .checkbox {
display: inline-block; display: inline-block;
cursor: pointer;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal; font-weight: normal;
font-size: .9em; font-size: .9em;
color: rgba(0, 0, 0, .5);
vertical-align:middle; vertical-align:middle;
> span { > span {