2019-08-21 17:20:31 -04:00
|
|
|
import React, { Fragment } from 'react';
|
2019-08-14 16:54:00 -04:00
|
|
|
import PropTypes from 'prop-types';
|
2019-08-21 17:20:31 -04:00
|
|
|
import { Redirect } from 'react-router-dom';
|
2019-08-14 16:54:00 -04:00
|
|
|
|
|
|
|
import BuildingNotFound from './building-not-found';
|
|
|
|
import ContainerHeader from './container-header';
|
|
|
|
import Sidebar from './sidebar';
|
2019-08-21 17:20:31 -04:00
|
|
|
import ErrorBox from '../components/error-box';
|
|
|
|
import InfoBox from '../components/info-box';
|
2019-08-14 16:54:00 -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
|
|
|
|
*/
|
|
|
|
const 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
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2019-08-21 17:20:31 -04:00
|
|
|
|
2019-08-14 16:54:00 -04:00
|
|
|
this.state = {
|
2019-08-21 17:20:31 -04:00
|
|
|
error: this.props.error || undefined,
|
|
|
|
like: this.props.like || undefined,
|
2019-08-14 16:54:00 -04:00
|
|
|
copying: false,
|
2019-08-21 17:20:31 -04:00
|
|
|
keys_to_copy: {},
|
2019-08-23 12:35:17 -04:00
|
|
|
building: this.props.building
|
2019-08-14 16:54:00 -04:00
|
|
|
};
|
2019-08-21 17:20:31 -04:00
|
|
|
|
|
|
|
this.handleChange = this.handleChange.bind(this);
|
|
|
|
this.handleCheck = this.handleCheck.bind(this);
|
|
|
|
this.handleLike = this.handleLike.bind(this);
|
|
|
|
this.handleSubmit = this.handleSubmit.bind(this);
|
|
|
|
this.handleUpdate = this.handleUpdate.bind(this);
|
|
|
|
|
2019-08-14 16:54:00 -04:00
|
|
|
this.toggleCopying = this.toggleCopying.bind(this);
|
|
|
|
this.toggleCopyAttribute = this.toggleCopyAttribute.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enter or exit "copying" state - allow user to select attributes to copy
|
|
|
|
*/
|
|
|
|
toggleCopying() {
|
|
|
|
this.setState({
|
|
|
|
copying: !this.state.copying
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Keep track of data to copy (accumulate while in "copying" state)
|
|
|
|
*
|
|
|
|
* @param {string} key
|
|
|
|
*/
|
2019-08-21 17:20:31 -04:00
|
|
|
toggleCopyAttribute(key: string) {
|
|
|
|
const keys = this.state.keys_to_copy;
|
|
|
|
if(this.state.keys_to_copy[key]){
|
|
|
|
delete keys[key];
|
2019-08-14 16:54:00 -04:00
|
|
|
} else {
|
2019-08-21 17:20:31 -04:00
|
|
|
keys[key] = true;
|
2019-08-14 16:54:00 -04:00
|
|
|
}
|
|
|
|
this.setState({
|
2019-08-21 17:20:31 -04:00
|
|
|
keys_to_copy: keys
|
2019-08-14 16:54:00 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-08-23 12:35:17 -04:00
|
|
|
updateBuildingState(key, value) {
|
|
|
|
const building = {...this.state.building};
|
|
|
|
building[key] = value;
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
building: building
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-08-21 17:20:31 -04:00
|
|
|
/**
|
|
|
|
* Handle changes on typical inputs
|
|
|
|
* - e.g. input[type=text], radio, select, textare
|
|
|
|
*
|
2019-08-23 12:35:17 -04:00
|
|
|
* @param {*} event
|
2019-08-21 17:20:31 -04:00
|
|
|
*/
|
|
|
|
handleChange(event) {
|
|
|
|
const target = event.target;
|
|
|
|
let value = (target.value === '')? null : target.value;
|
|
|
|
const name = target.name;
|
|
|
|
|
|
|
|
// special transform - consider something data driven before adding 'else if's
|
|
|
|
if (name === 'location_postcode' && value !== null) {
|
|
|
|
value = value.toUpperCase();
|
|
|
|
}
|
2019-08-23 12:35:17 -04:00
|
|
|
this.updateBuildingState(name, value);
|
2019-08-21 17:20:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle changes on checkboxes
|
|
|
|
* - e.g. input[type=checkbox]
|
|
|
|
*
|
2019-08-23 12:35:17 -04:00
|
|
|
* @param {*} event
|
2019-08-21 17:20:31 -04:00
|
|
|
*/
|
|
|
|
handleCheck(event) {
|
|
|
|
const target = event.target;
|
|
|
|
const value = target.checked;
|
|
|
|
const name = target.name;
|
|
|
|
|
2019-08-23 12:35:17 -04:00
|
|
|
this.updateBuildingState(name, value);
|
2019-08-21 17:20:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle update directly
|
|
|
|
* - e.g. as callback from MultiTextInput where we set a list of strings
|
|
|
|
*
|
2019-08-23 12:46:22 -04:00
|
|
|
* @param {String} name
|
2019-08-21 17:20:31 -04:00
|
|
|
* @param {*} value
|
|
|
|
*/
|
2019-08-23 12:46:22 -04:00
|
|
|
handleUpdate(name: string, value: any) {
|
2019-08-23 12:35:17 -04:00
|
|
|
this.updateBuildingState(name, value);
|
2019-08-21 17:20:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle likes separately
|
|
|
|
* - like/love reaction is limited to set/unset per user
|
|
|
|
*
|
2019-08-23 12:35:17 -04:00
|
|
|
* @param {*} event
|
2019-08-21 17:20:31 -04:00
|
|
|
*/
|
|
|
|
handleLike(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
const like = event.target.checked;
|
|
|
|
|
2019-08-23 12:35:17 -04:00
|
|
|
fetch(`/api/buildings/${this.props.building.building_id}/like.json`, {
|
2019-08-21 17:20:31 -04:00
|
|
|
method: 'POST',
|
|
|
|
headers:{
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'same-origin',
|
|
|
|
body: JSON.stringify({like: like})
|
|
|
|
}).then(
|
|
|
|
res => res.json()
|
|
|
|
).then(function(res){
|
|
|
|
if (res.error) {
|
|
|
|
this.setState({error: res.error})
|
|
|
|
} else {
|
|
|
|
this.props.selectBuilding(res);
|
2019-08-23 12:35:17 -04:00
|
|
|
this.updateBuildingState('likes_total', res.likes_total);
|
2019-08-21 17:20:31 -04:00
|
|
|
}
|
|
|
|
}.bind(this)).catch(
|
|
|
|
(err) => this.setState({error: err})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSubmit(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.setState({error: undefined})
|
|
|
|
|
2019-08-23 12:35:17 -04:00
|
|
|
fetch(`/api/buildings/${this.props.building.building_id}.json`, {
|
2019-08-21 17:20:31 -04:00
|
|
|
method: 'POST',
|
2019-08-23 12:35:17 -04:00
|
|
|
body: JSON.stringify(this.state.building),
|
2019-08-21 17:20:31 -04:00
|
|
|
headers:{
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'same-origin'
|
|
|
|
}).then(
|
|
|
|
res => res.json()
|
|
|
|
).then(function(res){
|
|
|
|
if (res.error) {
|
|
|
|
this.setState({error: res.error})
|
|
|
|
} else {
|
|
|
|
this.props.selectBuilding(res);
|
|
|
|
}
|
|
|
|
}.bind(this)).catch(
|
|
|
|
(err) => this.setState({error: err})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-08-14 16:54:00 -04:00
|
|
|
render() {
|
2019-08-21 17:20:31 -04:00
|
|
|
if (this.state.mode === 'edit' && !this.props.user){
|
|
|
|
return <Redirect to="/sign-up.html" />
|
|
|
|
}
|
|
|
|
|
|
|
|
const values_to_copy = {}
|
|
|
|
for (const key of Object.keys(this.state.keys_to_copy)) {
|
2019-08-23 12:35:17 -04:00
|
|
|
values_to_copy[key] = this.state.building[key]
|
2019-08-21 17:20:31 -04:00
|
|
|
}
|
|
|
|
const data_string = JSON.stringify(values_to_copy);
|
2019-08-14 16:54:00 -04:00
|
|
|
const copy = {
|
|
|
|
copying: this.state.copying,
|
|
|
|
toggleCopying: this.toggleCopying,
|
|
|
|
toggleCopyAttribute: this.toggleCopyAttribute,
|
2019-08-23 12:35:17 -04:00
|
|
|
copyingKey: (key) => this.state.keys_to_copy[key]
|
2019-08-14 16:54:00 -04:00
|
|
|
}
|
|
|
|
return this.props.building?
|
|
|
|
<Sidebar>
|
2019-08-21 17:20:31 -04:00
|
|
|
<section
|
|
|
|
id={this.props.slug}
|
|
|
|
className="data-section">
|
2019-08-23 12:35:17 -04:00
|
|
|
<ContainerHeader
|
|
|
|
{...this.props}
|
|
|
|
data_string={data_string}
|
|
|
|
copy={copy}
|
|
|
|
/>
|
2019-08-21 17:20:31 -04:00
|
|
|
<form
|
2019-08-23 12:35:17 -04:00
|
|
|
action={`/edit/${this.props.slug}/building/${this.props.building.building_id}.html`}
|
2019-08-21 17:20:31 -04:00
|
|
|
method="POST"
|
|
|
|
onSubmit={this.handleSubmit}>
|
|
|
|
<ErrorBox msg={this.state.error} />
|
|
|
|
{
|
|
|
|
(this.props.mode === 'edit' && this.props.inactive)?
|
|
|
|
<InfoBox
|
|
|
|
msg={`We're not collecting data on ${this.props.title.toLowerCase()} yet - check back soon.`}
|
|
|
|
/>
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
<WrappedComponent
|
2019-08-23 12:35:17 -04:00
|
|
|
building={this.state.building}
|
|
|
|
mode={this.props.mode}
|
2019-08-21 17:20:31 -04:00
|
|
|
copy={copy}
|
2019-08-23 12:35:17 -04:00
|
|
|
onChange={this.handleChange}
|
|
|
|
onCheck={this.handleCheck}
|
|
|
|
onLike={this.handleLike}
|
|
|
|
onUpdate={this.handleUpdate}
|
2019-08-21 17:20:31 -04:00
|
|
|
/>
|
|
|
|
{
|
|
|
|
(this.props.mode === 'edit' && !this.props.inactive)?
|
|
|
|
<Fragment>
|
|
|
|
<InfoBox
|
|
|
|
msg="Colouring may take a few seconds - try zooming the map or hitting refresh after saving (we're working on making this smoother)." />
|
|
|
|
{
|
|
|
|
this.props.slug === 'like'? // special-case for likes
|
|
|
|
null :
|
|
|
|
<div className="buttons-container">
|
|
|
|
<button
|
|
|
|
type="submit"
|
|
|
|
className="btn btn-primary">
|
|
|
|
Save
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</Fragment>
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
</form>
|
|
|
|
</section>
|
2019-08-14 16:54:00 -04:00
|
|
|
</Sidebar>
|
|
|
|
: <BuildingNotFound mode="view" />
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default withCopyEdit;
|