Unpack building edit to data-components with mode

This commit is contained in:
Tom Russell 2019-08-23 17:35:17 +01:00
parent b44b43bc31
commit 541a307b99
18 changed files with 665 additions and 685 deletions

View File

@ -1,344 +0,0 @@
import React, { Component, Fragment } from 'react';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import Tooltip from '../components/tooltip';
const TextInput = (props) => (
<Fragment>
<Label slug={props.slug} title={props.title} tooltip={props.tooltip}
copying={props.copying}
toggleCopyAttribute={props.toggleCopyAttribute}
copy={props.copy}
cat={props.cat}
disabled={props.disabled} />
<input className="form-control" type="text"
id={props.slug} name={props.slug}
value={props.value || ''}
maxLength={props.max_length}
disabled={props.disabled}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
</Fragment>
);
TextInput.propTypes = {
slug: PropTypes.string,
cat: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.string,
max_length: PropTypes.number,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
handleChange: PropTypes.func
}
const LongTextInput = (props) => (
<Fragment>
<Label slug={props.slug} title={props.title} cat={props.cat}
copying={props.copying}
toggleCopyAttribute={props.toggleCopyAttribute}
copy={props.copy}
disabled={props.disabled} tooltip={props.tooltip} />
<textarea className="form-control"
id={props.slug} name={props.slug}
disabled={props.disabled}
placeholder={props.placeholder}
onChange={props.handleChange}
value={props.value || ''}></textarea>
</Fragment>
)
LongTextInput.propTypes = {
slug: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.string,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
handleChange: PropTypes.func
}
class MultiTextInput extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
slug: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
handleChange: PropTypes.func,
copy: PropTypes.bool,
toggleCopyAttribute: PropTypes.func,
copying: PropTypes.bool
};
constructor(props) {
super(props);
this.edit = this.edit.bind(this);
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
this.getValues = this.getValues.bind(this);
}
getValues() {
return (this.props.value && this.props.value.length)? this.props.value : [null];
}
edit(event) {
const editIndex = +event.target.dataset.index;
const editItem = event.target.value;
const oldValues = this.getValues();
const values = oldValues.map((item, i) => {
return i === editIndex ? editItem : item;
});
this.props.handleChange(this.props.slug, values);
}
add(event) {
event.preventDefault();
const values = this.getValues().concat('');
this.props.handleChange(this.props.slug, values);
}
remove(event){
const removeIndex = +event.target.dataset.index;
const values = this.getValues().filter((_, i) => {
return i !== removeIndex;
});
this.props.handleChange(this.props.slug, values);
}
render() {
const values = this.getValues();
return (
<Fragment>
<Label slug={this.props.slug} title={this.props.title} tooltip={this.props.tooltip}
cat={this.props.cat}
copying={this.props.copying}
disabled={this.props.disabled}
toggleCopyAttribute={this.props.toggleCopyAttribute}
copy={this.props.copy} />
{
values.map((item, i) => (
<div className="input-group" key={i}>
<input className="form-control" type="text"
key={`${this.props.slug}-${i}`} name={`${this.props.slug}-${i}`}
data-index={i}
value={item || ''}
placeholder={this.props.placeholder}
disabled={this.props.disabled}
onChange={this.edit}
/>
<div className="input-group-append">
<button type="button" onClick={this.remove}
title="Remove"
data-index={i} className="btn btn-outline-dark"></button>
</div>
</div>
))
}
<button type="button" title="Add" onClick={this.add}
className="btn btn-outline-dark">+</button>
</Fragment>
)
}
}
const TextListInput = (props) => (
<Fragment>
<Label slug={props.slug} title={props.title} tooltip={props.tooltip}
cat={props.cat} disabled={props.disabled}
copying={props.copying}
toggleCopyAttribute={props.toggleCopyAttribute}
copy={props.copy} />
<select className="form-control"
id={props.slug} name={props.slug}
value={props.value || ''}
disabled={props.disabled}
// list={`${props.slug}_suggestions`} TODO: investigate whether this was needed
onChange={props.handleChange}>
<option value="">Select a source</option>
{
props.options.map(option => (
<option key={option} value={option}>{option}</option>
))
}
</select>
</Fragment>
)
TextListInput.propTypes = {
slug: PropTypes.string,
cat: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.string),
value: PropTypes.string,
disabled: PropTypes.bool,
handleChange: PropTypes.func
}
const NumberInput = (props) => (
<Fragment>
<Label slug={props.slug} title={props.title} tooltip={props.tooltip}
cat={props.cat} disabled={props.disabled}
copying={props.copying}
toggleCopyAttribute={props.toggleCopyAttribute}
copy={props.copy} />
<input className="form-control" type="number" step={props.step}
id={props.slug} name={props.slug}
value={props.value || ''}
disabled={props.disabled}
onChange={props.handleChange}
/>
</Fragment>
);
NumberInput.propTypes = {
slug: PropTypes.string,
cat: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
step: PropTypes.number,
value: PropTypes.number,
disabled: PropTypes.bool,
handleChange: PropTypes.func
}
class YearEstimator extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
slug: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
date_year: PropTypes.number,
date_upper: PropTypes.number,
date_lower: PropTypes.number,
value: PropTypes.number,
disabled: PropTypes.bool,
handleChange: PropTypes.func,
copy: PropTypes.bool,
toggleCopyAttribute: PropTypes.func,
copying: PropTypes.bool
};
constructor(props) {
super(props);
this.state = {
year: props.date_year,
upper: props.date_upper,
lower: props.date_lower,
decade: Math.floor(props.date_year / 10) * 10,
century: Math.floor(props.date_year / 100) * 100
}
}
// TODO add dropdown for decade, century
// TODO roll in first/last year estimate
// TODO handle changes internally, reporting out date_year, date_upper, date_lower
render() {
return (
<NumberInput {...this.props}
handleChange={this.props.handleChange}
value={this.props.value}
key={this.props.slug}
/>
)
}
}
const CheckboxInput = (props) => (
<Fragment>
<Label slug={props.slug} title={props.title} tooltip={props.tooltip}
cat={props.cat} disabled={props.disabled}
copying={props.copying}
toggleCopyAttribute={props.toggleCopyAttribute}
copy={props.copy} />
<div className="form-check">
<input className="form-check-input" type="checkbox"
id={props.slug} name={props.slug}
checked={!!props.value}
disabled={props.disabled}
onChange={props.handleChange}
/>
<label htmlFor={props.slug} className="form-check-label">
{props.title}
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</label>
</div>
</Fragment>
)
CheckboxInput.propTypes = {
slug: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.bool,
disabled: PropTypes.bool,
handleChange: PropTypes.func
}
const LikeButton = (props) => (
<Fragment>
<p className="likes">{(props.value)? props.value : 0} likes</p>
<div className="form-check">
<input className="form-check-input" type="checkbox"
id={props.slug} name={props.slug}
checked={!!props.building_like}
disabled={props.disabled}
onChange={props.handleLike}
/>
<label htmlFor={props.slug} className="form-check-label">
I like this building and think it contributes to the city!
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</label>
</div>
<p>
<NavLink
to={`/multi-edit/${props.cat}.html`}>
Like more buildings
</NavLink>
</p>
</Fragment>
);
LikeButton.propTypes = {
slug: PropTypes.string,
cat: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.number,
building_like: PropTypes.bool,
disabled: PropTypes.bool,
handleLike: PropTypes.func
}
const Label: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<label htmlFor={props.slug}>
{props.title}
{ (props.copying && props.cat && props.slug && !props.disabled)?
<div className="icon-buttons">
<label className="icon-button copy">
Copy
<input type="checkbox" checked={props.copy}
onChange={() => props.toggleCopyAttribute(props.slug)}/>
</label>
</div> : null
}
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</label>
);
}
Label.propTypes = {
slug: PropTypes.string,
cat: PropTypes.string,
title: PropTypes.string,
disabled: PropTypes.bool,
tooltip: PropTypes.string
}
export default BuildingEdit;

View File

@ -21,10 +21,15 @@ import LikeContainer from './data-containers/like';
* @param props
*/
const BuildingView = (props) => {
if (typeof(props.building) === "undefined"){
return <BuildingNotFound mode="view" />
}
switch (props.cat) {
case 'location':
return <LocationContainer
{...props}
key={props.building.building_id}
title="Location"
help="https://pages.colouring.london/location"
intro="Where are the buildings? Address, location and cross-references."
@ -32,6 +37,7 @@ const BuildingView = (props) => {
case 'use':
return <UseContainer
{...props}
key={props.building.building_id}
inactive={true}
title="Land Use"
intro="How are buildings used, and how does use change over time? Coming soon…"
@ -40,6 +46,7 @@ const BuildingView = (props) => {
case 'type':
return <TypeContainer
{...props}
key={props.building.building_id}
inactive={true}
title="Type"
intro="How were buildings previously used? Coming soon…"
@ -48,6 +55,7 @@ const BuildingView = (props) => {
case 'age':
return <AgeContainer
{...props}
key={props.building.building_id}
title="Age"
help="https://pages.colouring.london/age"
intro="Building age data can support energy analysis and help predict long-term change."
@ -55,6 +63,7 @@ const BuildingView = (props) => {
case 'size':
return <SizeContainer
{...props}
key={props.building.building_id}
title="Size &amp; Shape"
intro="How big are buildings?"
help="https://pages.colouring.london/shapeandsize"
@ -62,6 +71,7 @@ const BuildingView = (props) => {
case 'construction':
return <ConstructionContainer
{...props}
key={props.building.building_id}
title="Construction"
intro="How are buildings built? Coming soon…"
help="https://pages.colouring.london/construction"
@ -70,6 +80,7 @@ const BuildingView = (props) => {
case 'team':
return <TeamContainer
{...props}
key={props.building.building_id}
title="Team"
intro="Who built the buildings? Coming soon…"
help="https://pages.colouring.london/team"
@ -78,6 +89,7 @@ const BuildingView = (props) => {
case 'sustainability':
return <SustainabilityContainer
{...props}
key={props.building.building_id}
title="Sustainability"
intro="Are buildings energy efficient? Coming soon…"
help="https://pages.colouring.london/sustainability"
@ -86,6 +98,7 @@ const BuildingView = (props) => {
case 'greenery':
return <GreeneryContainer
{...props}
key={props.building.building_id}
title="Greenery"
intro="Is there greenery nearby? Coming soon…"
help="https://pages.colouring.london/greenery"
@ -94,6 +107,7 @@ const BuildingView = (props) => {
case 'community':
return <CommunityContainer
{...props}
key={props.building.building_id}
title="Community"
intro="How does this building work for the local community?"
help="https://pages.colouring.london/community"
@ -102,6 +116,7 @@ const BuildingView = (props) => {
case 'planning':
return <PlanningContainer
{...props}
key={props.building.building_id}
title="Planning"
intro="Planning controls relating to protection and reuse."
help="https://pages.colouring.london/planning"
@ -109,6 +124,7 @@ const BuildingView = (props) => {
case 'like':
return <LikeContainer
{...props}
key={props.building.building_id}
title="Like Me!"
intro="Do you like the building and think it contributes to the city?"
help="https://pages.colouring.london/likeme"

View File

@ -0,0 +1,50 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title';
const DataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
copy={props.copy}
/>
<div className="form-check">
<input className="form-check-input" type="checkbox"
id={props.slug}
name={props.slug}
checked={!!props.value}
disabled={props.mode === 'view' || props.disabled}
onChange={props.onChange}
/>
<label
htmlFor={props.slug}
className="form-check-label">
{props.title}
</label>
</div>
</Fragment>
);
}
DataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default DataEntry;

View File

@ -13,15 +13,15 @@ const DataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
disabled={props.disabled}
copy={props.copy}
/>
<dd>
{
(props.value != null && props.value !== '')?
(typeof(props.value) === 'boolean')?
(props.value)? 'Yes' : 'No'
: props.value
: '\u00A0'
}
</dd>
<input className="form-control" type="text"
id={props.slug}
name={props.slug}
value={props.value || ''}
maxLength={props.maxLength}
disabled={props.mode === 'view' || props.disabled}
placeholder={props.placeholder}
onChange={props.onChange}
/>
</Fragment>
);
}
@ -32,6 +32,9 @@ DataEntry.propTypes = {
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,

View File

@ -19,10 +19,9 @@ DataTitle.propTypes = {
const DataTitleCopyable: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<dt>
{ props.title }
<div className="data-title">
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
{ (props.copy.copying && props.slug && !props.disabled)?
{ (props.copy && props.copy.copying && props.slug && !props.disabled)?
<div className="icon-buttons">
<label className="icon-button copy">
Copy
@ -34,7 +33,10 @@ const DataTitleCopyable: React.FunctionComponent<any> = (props) => { // TODO: re
</div>
: null
}
</dt>
<label htmlFor={props.slug}>
{ props.title }
</label>
</div>
);
}

View File

@ -8,29 +8,35 @@ const LikeDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove
const data_string = JSON.stringify({like: true});
return (
<Fragment>
<dt>
Number of likes
<div className="data-title">
<Tooltip text="People who like the building and think it contributes to the city." />
<div className="icon-buttons">
<NavLink
to={`/multi-edit/like.html?data=${data_string}`}
className="icon-button copy">
Copy
className="icon-button like">
Like more
</NavLink>
</div>
</dt>
<dd>
<label>Number of likes</label>
</div>
<p>
{
(props.value != null)?
(props.value === 1)?
`${props.value} person likes this building`
: `${props.value} people like this building`
: '\u00A0'
: "0 people like this building so far - you could be the first!"
}
</dd>
{
(props.user_building_like)? <dd>&hellip;including you!</dd> : ''
}
</p>
<input className="form-check-input" type="checkbox"
id="like" name="like"
checked={!!props.building_like}
disabled={props.mode === 'view'}
onChange={props.handleLike}
/>
<label htmlFor="like" className="form-check-label">
I like this building and think it contributes to the city!
</label>
</Fragment>
);
}

View File

@ -1,38 +1,111 @@
import React, { Fragment } from 'react';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { sanitiseURL } from '../../helpers';
import { DataTitleCopyable } from './data-title';
const MultiDataEntry: React.FunctionComponent<any> = (props) => ( // TODO: remove any
<Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
/>
<dd>
{
(props.value && props.value.length)?
<ul>
{
props.value.map((item, index) => {
return <li key={index}><a href={sanitiseURL(item)}>{item}</a></li>
})
}
</ul>
:'\u00A0'
}
</dd>
</Fragment>
);
class MultiDataEntry extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
slug: PropTypes.string,
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
handleChange: PropTypes.func,
copy: PropTypes.bool,
toggleCopyAttribute: PropTypes.func,
copying: PropTypes.bool
};
MultiDataEntry.propTypes = {
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string)
constructor(props) {
super(props);
this.edit = this.edit.bind(this);
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
this.getValues = this.getValues.bind(this);
}
getValues() {
return (this.props.value && this.props.value.length)? this.props.value : [null];
}
edit(event) {
const editIndex = +event.target.dataset.index;
const editItem = event.target.value;
const oldValues = this.getValues();
const values = oldValues.map((item, i) => {
return i === editIndex ? editItem : item;
});
this.props.onChange(this.props.slug, values);
}
add(event) {
event.preventDefault();
const values = this.getValues().concat('');
this.props.onChange(this.props.slug, values);
}
remove(event){
const removeIndex = +event.target.dataset.index;
const values = this.getValues().filter((_, i) => {
return i !== removeIndex;
});
this.props.onChange(this.props.slug, values);
}
render() {
const values = this.getValues();
const props = this.props;
return <Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
/>
{
(props.mode === 'view')?
(props.value && props.value.length)?
<ul className="data-link-list">
{
props.value.map((item, index) => {
return <li
key={index}
className="form-control">
<a href={sanitiseURL(item)}>{item}</a>
</li>
})
}
</ul>
:'\u00A0'
: values.map((item, i) => (
<div className="input-group" key={i}>
<input className="form-control" type="text"
key={`${props.slug}-${i}`} name={`${props.slug}-${i}`}
data-index={i}
value={item || ''}
placeholder={props.placeholder}
disabled={props.disabled}
onChange={this.edit}
/>
<div className="input-group-append">
<button type="button" onClick={this.remove}
title="Remove"
data-index={i} className="btn btn-outline-dark"></button>
</div>
</div>
))
}
<button
type="button"
title="Add"
onClick={this.add}
disabled={props.mode === 'view'}
className="btn btn-outline-dark">+</button>
</Fragment>
}
}
export default MultiDataEntry;

View File

@ -0,0 +1,51 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title';
const NumericDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
copy={props.copy}
/>
<input
className="form-control"
type="number"
id={props.slug}
name={props.slug}
value={props.value || ''}
step={props.step || 1}
max={props.max}
min={props.min || 0}
disabled={props.mode === 'view' || props.disabled}
placeholder={props.placeholder}
onChange={props.onChange}
/>
</Fragment>
);
}
NumericDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
step: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default NumericDataEntry;

View File

@ -0,0 +1,48 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title';
const SelectDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
copy={props.copy}
/>
<select className="form-control"
id={props.slug} name={props.slug}
value={props.value || ''}
disabled={props.mode === 'view' || props.disabled}
onChange={props.handleChange}>
<option value="">{props.placeholder}</option>
{
props.options.map(option => (
<option key={option} value={option}>{option}</option>
))
}
</select>
</Fragment>
);
}
SelectDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default SelectDataEntry;

View File

@ -0,0 +1,47 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title';
const TextboxDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
return (
<Fragment>
<DataTitleCopyable
slug={props.slug}
title={props.title}
tooltip={props.tooltip}
disabled={props.disabled}
copy={props.copy}
/>
<textarea
className="form-control"
id={props.slug}
name={props.slug}
value={props.value || ''}
maxLength={props.max_length}
rows={5}
disabled={props.mode === 'view' || props.disabled}
placeholder={props.placeholder}
onChange={props.onChange}
></textarea>
</Fragment>
);
}
TextboxDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default TextboxDataEntry;

View File

@ -0,0 +1,71 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import NumericDataEntry from './numeric-data-entry';
class YearDataEntry extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
year: PropTypes.number,
upper: PropTypes.number,
lower: PropTypes.number,
mode: PropTypes.string,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
};
constructor(props) {
super(props);
this.state = {
year: props.year,
upper: props.upper,
lower: props.lower,
decade: Math.floor(props.year / 10) * 10,
century: Math.floor(props.year / 100) * 100
}
}
// TODO add dropdown for decade, century
// TODO roll in first/last year estimate
// TODO handle changes internally, reporting out date_year, date_upper, date_lower
render() {
const props = this.props;
return (
<Fragment>
<NumericDataEntry
title="Year built (best estimate)"
slug="date_year"
value={props.year}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
// "type": "year_estimator"
/>
<NumericDataEntry
title="Latest possible start year"
slug="date_upper"
value={props.upper}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={1}
tooltip="This should be the latest year in which building could have started."
/>
<NumericDataEntry
title="Earliest possible start date"
slug="date_lower"
value={props.lower}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={1}
tooltip="This should be the earliest year in which building could have started."
/>
</Fragment>
)
}
}
export default YearDataEntry;

View File

@ -31,18 +31,12 @@ const withCopyEdit = (WrappedComponent) => {
constructor(props) {
super(props);
// create object and spread into state to avoid TS complaining about modifying readonly state
let fieldsObj = {};
for (const field of props.fields) {
fieldsObj[field.slug] = props[field.slug];
}
this.state = {
error: this.props.error || undefined,
like: this.props.like || undefined,
copying: false,
keys_to_copy: {},
...fieldsObj
building: this.props.building
};
this.handleChange = this.handleChange.bind(this);
@ -81,11 +75,20 @@ const withCopyEdit = (WrappedComponent) => {
})
}
updateBuildingState(key, value) {
const building = {...this.state.building};
building[key] = value;
this.setState({
building: building
});
}
/**
* Handle changes on typical inputs
* - e.g. input[type=text], radio, select, textare
*
* @param {DocumentEvent} event
* @param {*} event
*/
handleChange(event) {
const target = event.target;
@ -96,25 +99,21 @@ const withCopyEdit = (WrappedComponent) => {
if (name === 'location_postcode' && value !== null) {
value = value.toUpperCase();
}
this.setState({
[name]: value
});
this.updateBuildingState(name, value);
}
/**
* Handle changes on checkboxes
* - e.g. input[type=checkbox]
*
* @param {DocumentEvent} event
* @param {*} event
*/
handleCheck(event) {
const target = event.target;
const value = target.checked;
const name = target.name;
this.setState({
[name]: value
});
this.updateBuildingState(name, value);
}
/**
@ -124,23 +123,21 @@ const withCopyEdit = (WrappedComponent) => {
* @param {String} key
* @param {*} value
*/
handleUpdate(key, value) {
this.setState({
[key]: value
});
handleUpdate(key: string, value: any) {
this.updateBuildingState(name, value);
}
/**
* Handle likes separately
* - like/love reaction is limited to set/unset per user
*
* @param {DocumentEvent} event
* @param {*} event
*/
handleLike(event) {
event.preventDefault();
const like = event.target.checked;
fetch(`/building/${this.props.building_id}/like.json`, {
fetch(`/api/buildings/${this.props.building.building_id}/like.json`, {
method: 'POST',
headers:{
'Content-Type': 'application/json'
@ -154,9 +151,7 @@ const withCopyEdit = (WrappedComponent) => {
this.setState({error: res.error})
} else {
this.props.selectBuilding(res);
this.setState({
likes_total: res.likes_total
})
this.updateBuildingState('likes_total', res.likes_total);
}
}.bind(this)).catch(
(err) => this.setState({error: err})
@ -167,9 +162,9 @@ const withCopyEdit = (WrappedComponent) => {
event.preventDefault();
this.setState({error: undefined})
fetch(`/building/${this.props.building_id}.json`, {
fetch(`/api/buildings/${this.props.building.building_id}.json`, {
method: 'POST',
body: JSON.stringify(this.state),
body: JSON.stringify(this.state.building),
headers:{
'Content-Type': 'application/json'
},
@ -194,29 +189,29 @@ const withCopyEdit = (WrappedComponent) => {
const values_to_copy = {}
for (const key of Object.keys(this.state.keys_to_copy)) {
values_to_copy[key] = this.state[key]
values_to_copy[key] = this.state.building[key]
}
const data_string = JSON.stringify(values_to_copy);
const copy = {
copying: this.state.copying,
toggleCopying: this.toggleCopying,
toggleCopyAttribute: this.toggleCopyAttribute,
copyingKey: (key) => Object.keys(this.state.values_to_copy).includes(key)
copyingKey: (key) => this.state.keys_to_copy[key]
}
return this.props.building?
<Sidebar>
<section
id={this.props.slug}
className="data-section">
<ContainerHeader
{...this.props}
data_string={data_string}
copy={copy}
/>
<form
action={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}
action={`/edit/${this.props.slug}/building/${this.props.building.building_id}.html`}
method="POST"
onSubmit={this.handleSubmit}>
<ContainerHeader
{...this.props}
data_string={data_string}
copy={copy}
/>
<ErrorBox msg={this.state.error} />
{
(this.props.mode === 'edit' && this.props.inactive)?
@ -226,12 +221,13 @@ const withCopyEdit = (WrappedComponent) => {
: null
}
<WrappedComponent
{...this.props}
building={this.state.building}
mode={this.props.mode}
copy={copy}
handleChange={this.handleChange}
handleCheck={this.handleCheck}
handleLike={this.handleLike}
handleUpdate={this.handleUpdate}
onChange={this.handleChange}
onCheck={this.handleCheck}
onLike={this.handleLike}
onUpdate={this.handleUpdate}
/>
{
(this.props.mode === 'edit' && !this.props.inactive)?

View File

@ -1,80 +1,74 @@
import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import MultiDataEntry from '../data-components/multi-data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import TextboxDataEntry from '../data-components/textbox-data-entry';
import YearDataEntry from '../data-components/year-data-entry';
/**
* Age view/edit section
*/
const AgeView = (props) => (
<Fragment>
<DataEntry
title="Year built (best estimate)"
slug="date_year"
value={props.building.date_year}
copy={props.copy}
// "type": "year_estimator"
<YearDataEntry
year={props.building.date_year}
upper={props.building.date_upper}
lower={props.building.date_lower}
mode={props.mode}
onChange={props.onChange}
/>
<DataEntry
title="Latest possible start year"
slug="date_upper"
value={props.building.date_upper}
copy={props.copy}
// "type": "number", "step": 1,
tooltip="This should be the latest year in which building could have started."
/>
<DataEntry
title="Earliest possible start date"
slug="date_lower"
value={props.building.date_lower}
copy={props.copy}
// "type": "number", "step": 1,
tooltip="This should be the earliest year in which building could have started."
/>
<DataEntry
<NumericDataEntry
title="Facade year"
slug="facade_year"
value={props.building.facade_year}
mode={props.mode}
copy={props.copy}
// "type": "number", "step": 1,
onChange={props.onChange}
step={1}
tooltip="Best estimate"
/>
<DataEntry
<SelectDataEntry
title="Source of information"
slug="date_source"
value={props.building.date_source}
mode={props.mode}
copy={props.copy}
// "type": "text_list",
onChange={props.onChange}
tooltip="Source for the main start date"
// "options": [
// "Survey of London",
// "Pevsner Guides",
// "Local history publication",
// "National Heritage List for England",
// "Historical map",
// "Archive research",
// "Expert knowledge of building",
// "Other book",
// "Other website",
// "Other"
// ]
placeholder=""
options={[
"Survey of London",
"Pevsner Guides",
"Local history publication",
"National Heritage List for England",
"Historical map",
"Archive research",
"Expert knowledge of building",
"Other book",
"Other website",
"Other"
]}
/>
<DataEntry
<TextboxDataEntry
title="Source details"
slug="date_source_detail"
value={props.building.date_source_detail}
mode={props.mode}
copy={props.copy}
// "type": "text_long",
onChange={props.onChange}
tooltip="References for date source (max 500 characters)"
/>
<DataEntry
<MultiDataEntry
title="Text and Image Links"
slug="date_link"
value={props.building.date_link}
mode={props.mode}
copy={props.copy}
// "type": "text_multi",
onChange={props.onChange}
tooltip="URL for age and date reference"
// "placeholder": "https://..."
placeholder="https://..."
/>
</Fragment>
)

View File

@ -10,6 +10,8 @@ const LikeView = (props) => (
<Fragment>
<LikeDataEntry
value={props.building.likes_total}
mode={props.mode}
onLike={props.onLike}
user_building_like={props.building_like}
/>
</Fragment>

View File

@ -2,127 +2,113 @@ import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import UPRNsDataEntry from '../data-components/uprns-data-entry';
import InfoBox from '../../components/info-box';
const LocationView = (props) => (
<Fragment>
<InfoBox msg="Text-based address fields are disabled at the moment. We're looking into how best to collect this data." />
<Fragment>
<InfoBox msg="Text-based address fields are disabled at the moment. We're looking into how best to collect this data." />
<DataEntry
title="Building Name"
slug="location_name"
value={props.building.location_name}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
tooltip="May not be needed for many buildings."
placeholder="Building name (if any)"
disabled={true}
/>
{
// "type": "text",
// "placeholder": "Building name (if any)",
}
<DataEntry
<NumericDataEntry
title="Building number"
slug="location_number"
value={props.building.location_number}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={1}
/>
{
// "type": "number",
// "step": 1
}
<DataEntry
title="Street"
slug="location_street"
value={props.building.location_street}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
/>
{
// "type": "text",
}
<DataEntry
title="Address line 2"
slug="location_line_two"
value={props.building.location_line_two}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
/>
{
// "type": "text",
}
<DataEntry
title="Town"
slug="location_town"
value={props.building.location_town}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
title="Postcode"
slug="location_postcode"
value={props.building.location_postcode}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
maxLength={8}
/>
{
// "type": "text",
// "max_length": 8
}
<DataEntry
title="TOID"
slug="ref_toid"
value={props.building.ref_toid}
mode={props.mode}
copy={props.copy}
tooltip="Ordnance Survey Topography Layer ID (to be filled automatically)"
onChange={props.onChange}
disabled={true}
/>
{
// "type": "text",
}
<UPRNsDataEntry
title="UPRNs"
value={props.building.uprns}
tooltip="Unique Property Reference Numbers (to be filled automatically)"
/>
{
// "type": "uprn_list",
}
<DataEntry
title="OSM ID"
slug="ref_osm_id"
value={props.building.ref_osm_id}
mode={props.mode}
copy={props.copy}
tooltip="OpenStreetMap feature ID"
maxLength={20}
onChange={props.onChange}
/>
{
// "type": "text",
// "max_length": 20
}
<DataEntry
<NumericDataEntry
title="Latitude"
slug="location_latitude"
value={props.building.location_latitude}
mode={props.mode}
copy={props.copy}
step={0.0001}
placeholder={51}
onChange={props.onChange}
/>
{
// "type": "number",
// "step": 0.0001,
// "placeholder": 51
}
<DataEntry
<NumericDataEntry
title="Longitude"
slug="location_longitude"
value={props.building.location_longitude}
mode={props.mode}
copy={props.copy}
step={0.0001}
placeholder={0}
onChange={props.onChange}
/>
{
// "type": "number",
// "step": 0.0001,
// "placeholder": 0
}
</Fragment>
</Fragment>
)
const LocationContainer = withCopyEdit(LocationView);

View File

@ -2,6 +2,8 @@ import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import CheckboxDataEntry from '../data-components/checkbox-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
/**
* Planning view/edit section
@ -12,207 +14,186 @@ const PlanningView = (props) => (
title="Planning portal link"
slug="planning_portal_link"
value={props.building.planning_portal_link}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="In a conservation area?"
slug="planning_in_conservation_area"
value={props.building.planning_in_conservation_area}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Conservation area name"
slug="planning_conservation_area_name"
value={props.building.planning_conservation_area_name}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="Is listed on the National Heritage List for England?"
slug="planning_in_list"
value={props.building.planning_in_list}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="National Heritage List for England list id"
slug="planning_list_id"
value={props.building.planning_list_id}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<SelectDataEntry
title="National Heritage List for England list type"
slug="planning_list_cat"
value={props.building.planning_list_cat}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
options={[
"Listed Building",
"Scheduled Monument",
"World Heritage Site",
"Building Preservation Notice",
"None"
]}
/>
{
// "type": "text_list",
// "options": [
// "Listed Building"
// "Scheduled Monument"
// "World Heritage Site"
// "Building Preservation Notice"
// "None"
// ]
}
<DataEntry
<SelectDataEntry
title="Listing grade"
slug="planning_list_grade"
value={props.building.planning_list_grade}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
options={[
"I",
"II*",
"II",
"None"
]}
/>
{
// "type": "text_list",
// "options": [
// "I"
// "II*"
// "II"
// "None"
// ]
}
<DataEntry
title="Heritage at risk list id"
slug="planning_heritage_at_risk_id"
value={props.building.planning_heritage_at_risk_id}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
title="World heritage list id"
slug="planning_world_list_id"
value={props.building.planning_world_list_id}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="In the Greater London Historic Environment Record?"
slug="planning_in_glher"
value={props.building.planning_in_glher}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Greater London Historic Environment Record link"
slug="planning_glher_url"
value={props.building.planning_glher_url}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="In an Architectural Priority Area?"
slug="planning_in_apa"
value={props.building.planning_in_apa}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Architectural Priority Area name"
slug="planning_apa_name"
value={props.building.planning_apa_name}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
title="Architectural Priority Area tier"
slug="planning_apa_tier"
value={props.building.planning_apa_tier}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="Is locally listed?"
slug="planning_in_local_list"
value={props.building.planning_in_local_list}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Local list link"
slug="planning_local_list_url"
value={props.building.planning_local_list_url}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="Within a historic area assessment?"
slug="planning_in_historic_area_assessment"
value={props.building.planning_in_historic_area_assessment}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Historic area assessment link"
slug="planning_historic_area_assessment_url"
value={props.building.planning_historic_area_assessment_url}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
{
// "type": "text"
}
<DataEntry
<CheckboxDataEntry
title="Is the building proposed for demolition?"
slug="planning_demolition_proposed"
value={props.building.planning_demolition_proposed}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
/>
{
// "type": "checkbox"
}
<DataEntry
<CheckboxDataEntry
title="Has the building been demolished?"
slug="planning_demolition_complete"
value={props.building.planning_demolition_complete}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
/>
{
// "type": "checkbox"
}
<DataEntry
title="Dates of construction and demolition of previous buildings on site"
slug="planning_demolition_history"
value={props.building.planning_demolition_history}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
/>
{
// "type": "text"
}
</Fragment>
)
const PlanningContainer = withCopyEdit(PlanningView);

View File

@ -1,150 +1,139 @@
import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
/**
* Size view/edit section
*/
const SizeView = (props) => (
<Fragment>
<DataEntry
<NumericDataEntry
title="Core storeys"
slug="size_storeys_core"
value={props.building.size_storeys_core}
mode={props.mode}
copy={props.copy}
tooltip="How many storeys between the pavement and start of roof?"
onChange={props.onChange}
step={1}
/>
{
// "type": "number",
// "step": 1,
}
<DataEntry
<NumericDataEntry
title="Attic storeys"
slug="size_storeys_attic"
value={props.building.size_storeys_attic}
mode={props.mode}
copy={props.copy}
tooltip="How many storeys above start of roof?"
onChange={props.onChange}
step={1}
/>
{
// "type": "number",
// "step": 1,
}
<DataEntry
<NumericDataEntry
title="Basement storeys"
slug="size_storeys_basement"
value={props.building.size_storeys_basement}
mode={props.mode}
copy={props.copy}
tooltip="How many storeys below pavement level?"
onChange={props.onChange}
step={1}
/>
{
// "type": "number",
// "step": 1,
}
<DataEntry
<NumericDataEntry
title="Height to apex (m)"
slug="size_height_apex"
value={props.building.size_height_apex}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="Height to eaves (m)"
slug="size_height_eaves"
value={props.building.size_height_eaves}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="Ground floor area (m²)"
slug="size_floor_area_ground"
value={props.building.size_floor_area_ground}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="Total floor area (m²)"
slug="size_floor_area_total"
value={props.building.size_floor_area_total}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="Frontage Width (m)"
slug="size_width_frontage"
value={props.building.size_width_frontage}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="Total area of plot (m²)"
slug="size_plot_area_total"
value={props.building.size_plot_area_total}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
disabled={true}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<NumericDataEntry
title="FAR ratio (percentage of plot covered by building)"
slug="size_far_ratio"
value={props.building.size_far_ratio}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
step={0.1}
disabled={true}
/>
{
// "type": "number",
// "step": 0.1
}
<DataEntry
<SelectDataEntry
title="Configuration (semi/detached, end/terrace)"
slug="size_configuration"
value={props.building.size_configuration}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
options={[
"Detached",
"Semi-detached",
"Terrace",
"End terrace",
"Block"
]}
/>
{
// "type": "text_list",
// "options": [
// "Detached",
// "Semi-detached",
// "Terrace",
// "End terrace",
// "Block"
// ]
}
<DataEntry
<SelectDataEntry
title="Roof shape"
slug="size_roof_shape"
value={props.building.size_roof_shape}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
disabled={true}
options={[
"Flat",
"Pitched",
"Other"
]}
/>
{
// "type": "text_list",
// "options": [
// "Flat",
// "Pitched",
// "Other"
// ]
}
</Fragment>
)
const SizeContainer = withCopyEdit(SizeView);

View File

@ -148,8 +148,7 @@
.icon-button.save:hover svg {
color: rgb(11, 225, 72);
}
label .icon-buttons,
.data-list dt .icon-buttons {
.data-title .icon-buttons {
float: right;
}
@ -225,3 +224,13 @@ label .icon-buttons,
list-style: none;
padding-left: 0;
}
.data-section .data-link-list {
padding: 0;
list-style: none;
margin-bottom: 0;
margin-top: 0.5rem;
}
.data-link-list li {
border-color: #6c757d;
border-radius: 0;
}