Avoid dependency loop
- building-view contained BuildingVIew and withCopyEdit - and imported each data-container - which each imported withCopyEdit to create their data-container seemed okay from ts/webpack dev environment but failed in jest test
This commit is contained in:
parent
22db157e6e
commit
1997c34470
@ -6,7 +6,6 @@ import { parse } from 'query-string';
|
|||||||
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
||||||
import './app.css';
|
import './app.css';
|
||||||
|
|
||||||
import BuildingEdit from './building/building-edit';
|
|
||||||
import BuildingView from './building/building-view';
|
import BuildingView from './building/building-view';
|
||||||
import ColouringMap from './map/map';
|
import ColouringMap from './map/map';
|
||||||
import Header from './header';
|
import Header from './header';
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import Tooltip from '../components/tooltip';
|
|
||||||
import { sanitiseURL } from '../helpers';
|
|
||||||
|
|
||||||
import BuildingNotFound from './building-not-found';
|
import BuildingNotFound from './building-not-found';
|
||||||
import ContainerHeader from './container-header';
|
|
||||||
import Sidebar from './sidebar';
|
|
||||||
|
|
||||||
import LocationContainer from './data-containers/location';
|
import LocationContainer from './data-containers/location';
|
||||||
import UseContainer from './data-containers/use';
|
import UseContainer from './data-containers/use';
|
||||||
@ -125,244 +118,4 @@ const BuildingView = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
copying: false,
|
|
||||||
values_to_copy: {}
|
|
||||||
};
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const DataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<dt>
|
|
||||||
{ props.title }
|
|
||||||
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
|
||||||
{ (props.copy.copying && props.cat && props.slug && !props.disabled)?
|
|
||||||
<div className="icon-buttons">
|
|
||||||
<label className="icon-button copy">
|
|
||||||
Copy
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={props.copy.copyingThis(props.slug)}
|
|
||||||
onChange={() => props.copy.toggleCopyAttribute(props.slug)}/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</dt>
|
|
||||||
<dd>{
|
|
||||||
(props.value != null && props.value !== '')?
|
|
||||||
(typeof(props.value) === 'boolean')?
|
|
||||||
(props.value)? 'Yes' : 'No'
|
|
||||||
: props.value
|
|
||||||
: '\u00A0'}</dd>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DataEntry.propTypes = {
|
|
||||||
title: PropTypes.string,
|
|
||||||
cat: PropTypes.string,
|
|
||||||
slug: PropTypes.string,
|
|
||||||
tooltip: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
value: PropTypes.any
|
|
||||||
}
|
|
||||||
|
|
||||||
const LikeDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
|
||||||
const data_string = JSON.stringify({like: true});
|
|
||||||
return (
|
|
||||||
<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>
|
|
||||||
{
|
|
||||||
(props.user_building_like)? <dd>…including you!</dd> : ''
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
LikeDataEntry.propTypes = {
|
|
||||||
title: PropTypes.string,
|
|
||||||
cat: PropTypes.string,
|
|
||||||
tooltip: PropTypes.string,
|
|
||||||
value: PropTypes.any,
|
|
||||||
user_building_like: PropTypes.bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const MultiDataEntry: React.FunctionComponent<any> = (props) => { // TODO: remove any
|
|
||||||
let content;
|
|
||||||
|
|
||||||
if (props.value && props.value.length) {
|
|
||||||
content = <ul>{
|
|
||||||
props.value.map((item, index) => {
|
|
||||||
return <li key={index}><a href={sanitiseURL(item)}>{item}</a></li>
|
|
||||||
})
|
|
||||||
}</ul>
|
|
||||||
} else {
|
|
||||||
content = '\u00A0'
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<dt>
|
|
||||||
{ props.title }
|
|
||||||
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
|
||||||
{ (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
|
|
||||||
}
|
|
||||||
</dt>
|
|
||||||
<dd>{ content }</dd>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiDataEntry.propTypes = {
|
|
||||||
title: PropTypes.string,
|
|
||||||
tooltip: PropTypes.string,
|
|
||||||
value: PropTypes.arrayOf(PropTypes.string)
|
|
||||||
}
|
|
||||||
|
|
||||||
const UPRNsDataEntry = (props) => {
|
|
||||||
const uprns = props.value || [];
|
|
||||||
const noParent = uprns.filter(uprn => uprn.parent_uprn == null);
|
|
||||||
const withParent = uprns.filter(uprn => uprn.parent_uprn != null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<dt>
|
|
||||||
{ props.title }
|
|
||||||
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
|
||||||
</dt>
|
|
||||||
<dd><ul className="uprn-list">
|
|
||||||
<Fragment>{
|
|
||||||
noParent.length?
|
|
||||||
noParent.map(uprn => (
|
|
||||||
<li key={uprn.uprn}>{uprn.uprn}</li>
|
|
||||||
))
|
|
||||||
: '\u00A0'
|
|
||||||
}</Fragment>
|
|
||||||
{
|
|
||||||
withParent.length?
|
|
||||||
<details>
|
|
||||||
<summary>Children</summary>
|
|
||||||
{
|
|
||||||
withParent.map(uprn => (
|
|
||||||
<li key={uprn.uprn}>{uprn.uprn} (child of {uprn.parent_uprn})</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</details>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</ul></dd>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
UPRNsDataEntry.propTypes = {
|
|
||||||
title: PropTypes.string,
|
|
||||||
tooltip: PropTypes.string,
|
|
||||||
value: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
uprn: PropTypes.string.isRequired,
|
|
||||||
parent_uprn: PropTypes.string
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BuildingView;
|
export default BuildingView;
|
||||||
export { withCopyEdit };
|
|
||||||
|
@ -49,7 +49,7 @@ const ContainerHeader: React.FunctionComponent<any> = (props) => (
|
|||||||
<NavLink
|
<NavLink
|
||||||
className="icon-button edit"
|
className="icon-button edit"
|
||||||
title="Edit data"
|
title="Edit data"
|
||||||
to={`/edit/${props.slug}/building/${props.building_id}.html`}>
|
to={`/edit/${props.cat}/building/${props.building.building_id}.html`}>
|
||||||
Edit
|
Edit
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
89
app/src/frontend/building/data-container.tsx
Normal file
89
app/src/frontend/building/data-container.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import BuildingNotFound from './building-not-found';
|
||||||
|
import ContainerHeader from './container-header';
|
||||||
|
import Sidebar from './sidebar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
this.state = {
|
||||||
|
copying: false,
|
||||||
|
values_to_copy: {}
|
||||||
|
};
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
toggleCopyAttribute(key) {
|
||||||
|
const value = this.props.building[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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const data_string = JSON.stringify(this.state.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)
|
||||||
|
}
|
||||||
|
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" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withCopyEdit;
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Age view/edit section
|
* Age view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Community view/edit section
|
* Community view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construction view/edit section
|
* Construction view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Greenery view/edit section
|
* Greenery view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like view/edit section
|
* Like view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
const LocationView = (props) => (
|
const LocationView = (props) => (
|
||||||
<dl className="data-list">
|
<dl className="data-list">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Planning view/edit section
|
* Planning view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size view/edit section
|
* Size view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sustainability view/edit section
|
* Sustainability view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Team view/edit section
|
* Team view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type view/edit section
|
* Type view/edit section
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { withCopyEdit } from '../building-view';
|
import withCopyEdit from '../data-container';
|
||||||
|
import DataEntry from '../data-components/data-entry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use view/edit section
|
* Use view/edit section
|
||||||
|
Loading…
Reference in New Issue
Block a user