2018-10-01 07:45:33 -04:00
|
|
|
import React, { Fragment } from 'react';
|
2019-08-14 14:33:26 -04:00
|
|
|
import { NavLink } from 'react-router-dom';
|
2019-05-27 13:26:29 -04:00
|
|
|
import PropTypes from 'prop-types';
|
2018-09-09 17:22:44 -04:00
|
|
|
|
2019-08-14 04:21:42 -04:00
|
|
|
import Tooltip from '../components/tooltip';
|
|
|
|
import { sanitiseURL } from '../helpers';
|
2018-09-11 15:59:59 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
import BuildingNotFound from './building-not-found';
|
|
|
|
import ContainerHeader from './container-header';
|
|
|
|
import Sidebar from './sidebar';
|
2019-08-14 06:36:38 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
import LocationContainer from './data-containers/location';
|
|
|
|
import UseContainer from './data-containers/use';
|
|
|
|
import TypeContainer from './data-containers/type';
|
|
|
|
import AgeContainer from './data-containers/age';
|
|
|
|
import SizeContainer from './data-containers/size';
|
|
|
|
import ConstructionContainer from './data-containers/construction';
|
|
|
|
import TeamContainer from './data-containers/team';
|
|
|
|
import SustainabilityContainer from './data-containers/sustainability';
|
|
|
|
import GreeneryContainer from './data-containers/greenery';
|
|
|
|
import CommunityContainer from './data-containers/community';
|
|
|
|
import PlanningContainer from './data-containers/planning';
|
|
|
|
import LikeContainer from './data-containers/like';
|
2019-08-14 06:36:38 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
/**
|
|
|
|
* Top-level container for building view/edit form
|
|
|
|
*
|
|
|
|
* @param props
|
|
|
|
*/
|
|
|
|
const BuildingView = (props) => {
|
|
|
|
switch (props.cat) {
|
|
|
|
case 'location':
|
|
|
|
return <LocationContainer
|
2019-08-14 06:36:38 -04:00
|
|
|
{...props}
|
2019-08-14 14:33:26 -04:00
|
|
|
title="Location"
|
|
|
|
help="https://pages.colouring.london/location"
|
|
|
|
intro="Where are the buildings? Address, location and cross-references."
|
2019-08-14 06:36:38 -04:00
|
|
|
/>
|
2019-08-14 14:33:26 -04:00
|
|
|
case 'use':
|
|
|
|
return <UseContainer
|
|
|
|
{...props}
|
|
|
|
inactive={true}
|
|
|
|
title="Land Use"
|
|
|
|
intro="How are buildings used, and how does use change over time? Coming soon…"
|
|
|
|
help="https://pages.colouring.london/use"
|
|
|
|
/>
|
|
|
|
case 'type':
|
|
|
|
return <TypeContainer
|
|
|
|
{...props}
|
|
|
|
inactive={true}
|
|
|
|
title="Type"
|
|
|
|
intro="How were buildings previously used? Coming soon…"
|
|
|
|
help="https://www.pages.colouring.london/buildingtypology"
|
|
|
|
/>
|
|
|
|
case 'age':
|
|
|
|
return <AgeContainer
|
|
|
|
{...props}
|
|
|
|
title="Age"
|
|
|
|
help="https://pages.colouring.london/age"
|
|
|
|
intro="Building age data can support energy analysis and help predict long-term change."
|
|
|
|
/>
|
|
|
|
case 'size':
|
|
|
|
return <SizeContainer
|
|
|
|
{...props}
|
|
|
|
title="Size & Shape"
|
|
|
|
intro="How big are buildings?"
|
|
|
|
help="https://pages.colouring.london/shapeandsize"
|
|
|
|
/>
|
|
|
|
case 'construction':
|
|
|
|
return <ConstructionContainer
|
|
|
|
{...props}
|
|
|
|
title="Construction"
|
|
|
|
intro="How are buildings built? Coming soon…"
|
|
|
|
help="https://pages.colouring.london/construction"
|
|
|
|
inactive={true}
|
|
|
|
/>
|
|
|
|
case 'team':
|
|
|
|
return <TeamContainer
|
|
|
|
{...props}
|
|
|
|
title="Team"
|
|
|
|
intro="Who built the buildings? Coming soon…"
|
|
|
|
help="https://pages.colouring.london/team"
|
|
|
|
inactive={true}
|
|
|
|
/>
|
|
|
|
case 'sustainability':
|
|
|
|
return <SustainabilityContainer
|
|
|
|
{...props}
|
|
|
|
title="Sustainability"
|
|
|
|
intro="Are buildings energy efficient? Coming soon…"
|
|
|
|
help="https://pages.colouring.london/sustainability"
|
|
|
|
inactive={true}
|
|
|
|
/>
|
|
|
|
case 'greenery':
|
|
|
|
return <GreeneryContainer
|
|
|
|
{...props}
|
|
|
|
title="Greenery"
|
|
|
|
intro="Is there greenery nearby? Coming soon…"
|
|
|
|
help="https://pages.colouring.london/greenery"
|
|
|
|
inactive={true}
|
|
|
|
/>
|
|
|
|
case 'community':
|
|
|
|
return <CommunityContainer
|
|
|
|
{...props}
|
|
|
|
title="Community"
|
|
|
|
intro="How does this building work for the local community?"
|
|
|
|
help="https://pages.colouring.london/community"
|
|
|
|
inactive={true}
|
|
|
|
/>
|
|
|
|
case 'planning':
|
|
|
|
return <PlanningContainer
|
|
|
|
{...props}
|
|
|
|
title="Planning"
|
|
|
|
intro="Planning controls relating to protection and reuse."
|
|
|
|
help="https://pages.colouring.london/planning"
|
|
|
|
/>
|
|
|
|
case 'like':
|
|
|
|
return <LikeContainer
|
|
|
|
{...props}
|
|
|
|
title="Like Me!"
|
|
|
|
intro="Do you like the building and think it contributes to the city?"
|
|
|
|
help="https://pages.colouring.london/likeme"
|
|
|
|
/>
|
|
|
|
default:
|
|
|
|
return <BuildingNotFound mode="view" />
|
|
|
|
}
|
2019-05-27 13:26:29 -04:00
|
|
|
}
|
2018-10-03 16:47:49 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
/**
|
|
|
|
* Shared functionality for view/edit forms
|
|
|
|
*
|
|
|
|
* See React Higher-order-component docs for the pattern
|
|
|
|
* - https://reactjs.org/docs/higher-order-components.html
|
|
|
|
*
|
|
|
|
* @param WrappedComponent
|
|
|
|
*/
|
|
|
|
function withCopyEdit(WrappedComponent) {
|
|
|
|
return class extends React.Component<any, any> { // TODO: add proper types
|
|
|
|
static propTypes = { // TODO: generate propTypes from TS
|
|
|
|
title: PropTypes.string,
|
|
|
|
slug: PropTypes.string,
|
|
|
|
intro: PropTypes.string,
|
|
|
|
help: PropTypes.string,
|
|
|
|
inactive: PropTypes.bool,
|
|
|
|
building_id: PropTypes.number,
|
|
|
|
children: PropTypes.node
|
2019-08-01 06:19:04 -04:00
|
|
|
};
|
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
copying: false,
|
|
|
|
values_to_copy: {}
|
|
|
|
};
|
|
|
|
this.toggleCopying = this.toggleCopying.bind(this);
|
|
|
|
this.toggleCopyAttribute = this.toggleCopyAttribute.bind(this);
|
2019-08-01 06:19:04 -04:00
|
|
|
}
|
2019-08-06 17:11:17 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
/**
|
|
|
|
* Enter or exit "copying" state - allow user to select attributes to copy
|
|
|
|
*/
|
|
|
|
toggleCopying() {
|
|
|
|
this.setState({
|
|
|
|
copying: !this.state.copying
|
|
|
|
})
|
|
|
|
}
|
2019-08-01 06:19:04 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
/**
|
|
|
|
* Keep track of data to copy (accumulate while in "copying" state)
|
|
|
|
*
|
|
|
|
* @param {string} key
|
|
|
|
*/
|
|
|
|
toggleCopyAttribute(key) {
|
|
|
|
const value = this.props[key];
|
|
|
|
const values = this.state.values_to_copy;
|
|
|
|
if(Object.keys(this.state.values_to_copy).includes(key)){
|
|
|
|
delete values[key];
|
|
|
|
} else {
|
|
|
|
values[key] = value;
|
|
|
|
}
|
|
|
|
this.setState({
|
|
|
|
values_to_copy: values
|
|
|
|
})
|
|
|
|
}
|
2019-08-01 06:19:04 -04:00
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
render() {
|
|
|
|
const data_string = JSON.stringify(this.state.values_to_copy);
|
|
|
|
const copy = {
|
|
|
|
copying: this.state.copying,
|
|
|
|
toggleCopyAttribute: this.toggleCopyAttribute,
|
|
|
|
copyingKey: (key) => Object.keys(this.state.values_to_copy).includes(key)
|
|
|
|
}
|
|
|
|
return this.props.building?
|
|
|
|
<Sidebar>
|
|
|
|
<section id={this.props.slug} className="data-section">
|
|
|
|
<ContainerHeader {...this.props} data_string={data_string} copy={copy} />
|
|
|
|
<WrappedComponent {...this.props} copy={copy} />
|
|
|
|
</section>
|
|
|
|
</Sidebar>
|
|
|
|
: <BuildingNotFound mode="view" />
|
|
|
|
}
|
2019-08-01 06:19:04 -04:00
|
|
|
}
|
2018-09-30 19:32:24 -04:00
|
|
|
}
|
|
|
|
|
2019-08-14 14:33:26 -04:00
|
|
|
|
2019-08-14 03:28:15 -04:00
|
|
|
const DataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
2019-08-01 05:29:32 -04:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<dt>
|
|
|
|
{ props.title }
|
|
|
|
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
2019-08-14 14:33:26 -04:00
|
|
|
{ (props.copy.copying && props.cat && props.slug && !props.disabled)?
|
2019-08-01 05:29:32 -04:00
|
|
|
<div className="icon-buttons">
|
2019-08-01 06:19:04 -04:00
|
|
|
<label className="icon-button copy">
|
2019-08-01 05:29:32 -04:00
|
|
|
Copy
|
2019-08-14 14:33:26 -04:00
|
|
|
<input
|
|
|
|
type="checkbox"
|
|
|
|
checked={props.copy.copyingThis(props.slug)}
|
|
|
|
onChange={() => props.copy.toggleCopyAttribute(props.slug)}/>
|
2019-08-01 06:19:04 -04:00
|
|
|
</label>
|
2019-08-01 05:29:32 -04:00
|
|
|
</div>
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
</dt>
|
|
|
|
<dd>{
|
|
|
|
(props.value != null && props.value !== '')?
|
|
|
|
(typeof(props.value) === 'boolean')?
|
|
|
|
(props.value)? 'Yes' : 'No'
|
|
|
|
: props.value
|
|
|
|
: '\u00A0'}</dd>
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
2018-09-09 17:22:44 -04:00
|
|
|
|
2019-05-27 13:26:29 -04:00
|
|
|
DataEntry.propTypes = {
|
|
|
|
title: PropTypes.string,
|
2019-05-27 15:13:43 -04:00
|
|
|
cat: PropTypes.string,
|
|
|
|
slug: PropTypes.string,
|
2019-05-27 13:26:29 -04:00
|
|
|
tooltip: PropTypes.string,
|
2019-05-27 15:54:43 -04:00
|
|
|
disabled: PropTypes.bool,
|
2019-05-27 13:26:29 -04:00
|
|
|
value: PropTypes.any
|
|
|
|
}
|
|
|
|
|
2019-08-14 03:28:15 -04:00
|
|
|
const LikeDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
2019-08-01 05:29:32 -04:00
|
|
|
const data_string = JSON.stringify({like: true});
|
2019-08-09 13:49:43 -04:00
|
|
|
return (
|
2019-08-01 05:29:32 -04:00
|
|
|
<Fragment>
|
|
|
|
<dt>
|
|
|
|
{ props.title }
|
|
|
|
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
|
|
|
<div className="icon-buttons">
|
|
|
|
<NavLink
|
|
|
|
to={`/multi-edit/${props.cat}.html?data=${data_string}`}
|
|
|
|
className="icon-button copy">
|
|
|
|
Copy
|
|
|
|
</NavLink>
|
|
|
|
</div>
|
|
|
|
</dt>
|
|
|
|
<dd>
|
|
|
|
{
|
|
|
|
(props.value != null)?
|
|
|
|
(props.value === 1)?
|
|
|
|
`${props.value} person likes this building`
|
|
|
|
: `${props.value} people like this building`
|
|
|
|
: '\u00A0'
|
|
|
|
}
|
|
|
|
</dd>
|
2019-05-27 11:39:16 -04:00
|
|
|
{
|
2019-08-12 16:59:24 -04:00
|
|
|
(props.user_building_like)? <dd>…including you!</dd> : ''
|
2019-05-27 11:39:16 -04:00
|
|
|
}
|
2019-08-01 05:29:32 -04:00
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
2019-01-22 12:21:44 -05:00
|
|
|
|
2019-05-27 13:26:29 -04:00
|
|
|
LikeDataEntry.propTypes = {
|
|
|
|
title: PropTypes.string,
|
2019-05-27 15:13:43 -04:00
|
|
|
cat: PropTypes.string,
|
2019-05-27 13:26:29 -04:00
|
|
|
tooltip: PropTypes.string,
|
|
|
|
value: PropTypes.any,
|
|
|
|
user_building_like: PropTypes.bool
|
|
|
|
}
|
|
|
|
|
2019-08-14 03:28:15 -04:00
|
|
|
const MultiDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
2019-01-19 13:47:08 -05:00
|
|
|
let content;
|
|
|
|
|
|
|
|
if (props.value && props.value.length) {
|
|
|
|
content = <ul>{
|
|
|
|
props.value.map((item, index) => {
|
2019-05-27 13:26:29 -04:00
|
|
|
return <li key={index}><a href={sanitiseURL(item)}>{item}</a></li>
|
2019-01-19 13:47:08 -05:00
|
|
|
})
|
|
|
|
}</ul>
|
|
|
|
} else {
|
|
|
|
content = '\u00A0'
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<dt>
|
|
|
|
{ props.title }
|
|
|
|
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
2019-08-06 17:11:17 -04:00
|
|
|
{ (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
|
|
|
|
}
|
2019-01-19 13:47:08 -05:00
|
|
|
</dt>
|
|
|
|
<dd>{ content }</dd>
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-27 13:26:29 -04:00
|
|
|
MultiDataEntry.propTypes = {
|
|
|
|
title: PropTypes.string,
|
|
|
|
tooltip: PropTypes.string,
|
|
|
|
value: PropTypes.arrayOf(PropTypes.string)
|
|
|
|
}
|
|
|
|
|
2018-10-25 09:36:52 -04:00
|
|
|
const UPRNsDataEntry = (props) => {
|
|
|
|
const uprns = props.value || [];
|
2019-05-27 13:26:29 -04:00
|
|
|
const noParent = uprns.filter(uprn => uprn.parent_uprn == null);
|
|
|
|
const withParent = uprns.filter(uprn => uprn.parent_uprn != null);
|
2018-10-25 09:36:52 -04:00
|
|
|
|
|
|
|
return (
|
2019-05-27 11:39:16 -04:00
|
|
|
<Fragment>
|
|
|
|
<dt>
|
|
|
|
{ props.title }
|
|
|
|
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
|
|
|
</dt>
|
|
|
|
<dd><ul className="uprn-list">
|
|
|
|
<Fragment>{
|
2019-05-27 13:26:29 -04:00
|
|
|
noParent.length?
|
|
|
|
noParent.map(uprn => (
|
2019-05-27 11:39:16 -04:00
|
|
|
<li key={uprn.uprn}>{uprn.uprn}</li>
|
2018-10-25 09:36:52 -04:00
|
|
|
))
|
2019-05-27 11:39:16 -04:00
|
|
|
: '\u00A0'
|
|
|
|
}</Fragment>
|
|
|
|
{
|
2019-05-27 13:26:29 -04:00
|
|
|
withParent.length?
|
2019-05-27 11:39:16 -04:00
|
|
|
<details>
|
|
|
|
<summary>Children</summary>
|
|
|
|
{
|
2019-05-27 13:26:29 -04:00
|
|
|
withParent.map(uprn => (
|
2019-05-27 11:39:16 -04:00
|
|
|
<li key={uprn.uprn}>{uprn.uprn} (child of {uprn.parent_uprn})</li>
|
|
|
|
))
|
|
|
|
}
|
|
|
|
</details>
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
</ul></dd>
|
|
|
|
</Fragment>
|
2018-10-25 09:36:52 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-05-27 13:26:29 -04:00
|
|
|
UPRNsDataEntry.propTypes = {
|
|
|
|
title: PropTypes.string,
|
|
|
|
tooltip: PropTypes.string,
|
|
|
|
value: PropTypes.arrayOf(PropTypes.shape({
|
|
|
|
uprn: PropTypes.string.isRequired,
|
|
|
|
parent_uprn: PropTypes.string
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2018-09-09 17:22:44 -04:00
|
|
|
export default BuildingView;
|
2019-08-14 14:33:26 -04:00
|
|
|
export { withCopyEdit };
|