Lint 4-space indent

This commit is contained in:
Tom Russell 2019-05-27 16:39:16 +01:00
parent 907afa29f0
commit af9c23d883
25 changed files with 622 additions and 603 deletions

View File

@ -21,7 +21,8 @@
"no-console": "off",
"strict": ["error", "global"],
"curly": "warn",
"quotes": ["warn", "single"]
"quotes": ["warn", "single"],
"indent": ["warn", 4]
},
"plugins": [
"react"

View File

@ -11,12 +11,12 @@ import App from './frontend/app';
const data = window.__PRELOADED_STATE__;
hydrate(
<BrowserRouter>
<App user={data.user} building={data.building} building_like={data.building_like} />
</BrowserRouter>,
document.getElementById('root')
<BrowserRouter>
<App user={data.user} building={data.building} building_like={data.building_like} />
</BrowserRouter>,
document.getElementById('root')
);
if (module.hot) {
module.hot.accept();
module.hot.accept();
}

View File

@ -5,51 +5,51 @@ import './about.css';
const AboutPage = () => (
<article>
<section className="main-col">
<h1 className="h2">
<section className="main-col">
<h1 className="h2">
Can you help us capture information on every building in London?
</h1>
<p>
</h1>
<p>
How many buildings are there in London? What are their characteristics? Where
are they located and how do they contribute to the city? How adaptable are
they? How long will they last, and what are the environmental and
socio-economic implications of demolition?
</p>
<p>
</p>
<p>
Colouring London is being designed to address these questions by crowdsourcing
and visualising information on Londons buildings. Were releasing the
prototype for testing in the late summer. See the slideshow below for what it
will look like.
</p>
<div className="buttons-container btn-center">
<a className="btn btn-outline-dark btn-lg" href="#sign-up">Sign up for updates</a>
</div>
<div className="carousel">
<button className="carousel-control offscreen-text back">Back</button>
<ul className="carousel-content">
<li><img src="images/slide-1-welcome.png" alt="Welcome" /></li>
<li><img src="images/slide-2-categories.png" alt="Categories" /></li>
<li><img src="images/slide-3-edit.png" alt="Add/edit data" /></li>
<li><img src="images/slide-4-view.png" alt="View maps" /></li>
<li><img src="images/slide-5-download.png" alt="Download data" /></li>
<li><img src="images/slide-6-showcase.png" alt="Showcase" /></li>
<li><img src="images/slide-7-partners.png" alt="Partners" /></li>
</ul>
<button className="carousel-control offscreen-text next">Next</button>
</div>
<div className="buttons-container btn-center">
<a className="btn btn-outline-dark btn-lg"
href="files/colouring-london-online-exhibition.pdf">
</p>
<div className="buttons-container btn-center">
<a className="btn btn-outline-dark btn-lg" href="#sign-up">Sign up for updates</a>
</div>
<div className="carousel">
<button className="carousel-control offscreen-text back">Back</button>
<ul className="carousel-content">
<li><img src="images/slide-1-welcome.png" alt="Welcome" /></li>
<li><img src="images/slide-2-categories.png" alt="Categories" /></li>
<li><img src="images/slide-3-edit.png" alt="Add/edit data" /></li>
<li><img src="images/slide-4-view.png" alt="View maps" /></li>
<li><img src="images/slide-5-download.png" alt="Download data" /></li>
<li><img src="images/slide-6-showcase.png" alt="Showcase" /></li>
<li><img src="images/slide-7-partners.png" alt="Partners" /></li>
</ul>
<button className="carousel-control offscreen-text next">Next</button>
</div>
<div className="buttons-container btn-center">
<a className="btn btn-outline-dark btn-lg"
href="files/colouring-london-online-exhibition.pdf">
View online exhibition</a>
</div>
</section>
<hr/>
<section className="main-col">
<p>
</div>
</section>
<hr/>
<section className="main-col">
<p>
Colouring London is being designed and built by the Centre for Advanced Spatial
Analysis (CASA), University College London and funded by Historic England.
@ -57,159 +57,159 @@ const AboutPage = () => (
facilitated by the GLA, and giving access to its API and technical support. It
will launch in 2019.
</p>
<SupporterLogos />
</section>
<hr/>
<div className="main-col">
<h2 className="h1">Data Categories</h2>
<p>
</p>
<SupporterLogos />
</section>
<hr/>
<div className="main-col">
<h2 className="h1">Data Categories</h2>
<p>
12 categories have been chosen in consultation with specialists working in a
range of areas, from energy analysis and sustainable urban planning and design
to building conservation, community planning, architecture and historical
research.
</p>
<ol className="data-category-list">
<li className="bold-yellow">
<h3 className="category">Location</h3>
<p className="description">Where is it?</p>
</li>
<li className="bright-yellow">
<h3 className="category">Use</h3>
<p className="description">How is it used?</p>
</li>
<li className="bold-orange">
<h3 className="category">Type</h3>
<p className="description">How was it first used?</p>
</li>
<li className="red">
<h3 className="category">Age</h3>
<p className="description">When was it built?</p>
</li>
<li className="pastel-pink">
<h3 className="category">Size</h3>
<p className="description">How big is it?</p>
</li>
<li className="pastel-purple">
<h3 className="category">Construction</h3>
<p className="description">How is it built?</p>
</li>
<li className="blue-grey">
<h3 className="category">Design/Build</h3>
<p className="description">Who built it?</p>
</li>
<li className="bright-green">
<h3 className="category">Street Front</h3>
<p className="description">How does it relate to the street?</p>
</li>
<li className="pastel-green">
<h3 className="category">Greenery</h3>
<p className="description">Is it near a tree or park?</p>
</li>
<li className="bright-blue">
<h3 className="category">Protection</h3>
<p className="description">Is it designated?</p>
</li>
<li className="pale-grey">
<h3 className="category">Demolitions</h3>
<p className="description">How many rebuilds on the site?</p>
</li>
<li className="pale-brown">
<h3 className="category">Like Me?</h3>
<p className="description">Do you like it?</p>
</li>
</ol>
</div>
<hr/>
<div className="main-col">
<h2 className="h1">Once built, our platform will allow you to:</h2>
</div>
<section className="pale-pink color-block">
</p>
<ol className="data-category-list">
<li className="bold-yellow">
<h3 className="category">Location</h3>
<p className="description">Where is it?</p>
</li>
<li className="bright-yellow">
<h3 className="category">Use</h3>
<p className="description">How is it used?</p>
</li>
<li className="bold-orange">
<h3 className="category">Type</h3>
<p className="description">How was it first used?</p>
</li>
<li className="red">
<h3 className="category">Age</h3>
<p className="description">When was it built?</p>
</li>
<li className="pastel-pink">
<h3 className="category">Size</h3>
<p className="description">How big is it?</p>
</li>
<li className="pastel-purple">
<h3 className="category">Construction</h3>
<p className="description">How is it built?</p>
</li>
<li className="blue-grey">
<h3 className="category">Design/Build</h3>
<p className="description">Who built it?</p>
</li>
<li className="bright-green">
<h3 className="category">Street Front</h3>
<p className="description">How does it relate to the street?</p>
</li>
<li className="pastel-green">
<h3 className="category">Greenery</h3>
<p className="description">Is it near a tree or park?</p>
</li>
<li className="bright-blue">
<h3 className="category">Protection</h3>
<p className="description">Is it designated?</p>
</li>
<li className="pale-grey">
<h3 className="category">Demolitions</h3>
<p className="description">How many rebuilds on the site?</p>
</li>
<li className="pale-brown">
<h3 className="category">Like Me?</h3>
<p className="description">Do you like it?</p>
</li>
</ol>
</div>
<hr/>
<div className="main-col">
<h3 className="h2">View maps</h3>
<p>
<h2 className="h1">Once built, our platform will allow you to:</h2>
</div>
<section className="pale-pink color-block">
<div className="main-col">
<h3 className="h2">View maps</h3>
<p>
To view the data, navigate to the View Maps page and find the category that
interests you.
</p>
<img className="border-image" src="images/slide-4-view.png"
alt="Preview of view maps page" />
</div>
</section>
<section className="pale-yellow color-block">
<div className="main-col">
<h3 className="h2">Add and edit data</h3>
<p>
</p>
<img className="border-image" src="images/slide-4-view.png"
alt="Preview of view maps page" />
</div>
</section>
<section className="pale-yellow color-block">
<div className="main-col">
<h3 className="h2">Add and edit data</h3>
<p>
Find a building and add or edit data for any of the 12 core categories.
</p>
<img className="border-image" src="images/slide-3-edit.png"
alt="Preview of add/edit data page" />
</div>
</section>
<section className="pale-orange color-block">
<div className="main-col">
<h3 className="h2">See how people are using our data</h3>
<p>
</p>
<img className="border-image" src="images/slide-3-edit.png"
alt="Preview of add/edit data page" />
</div>
</section>
<section className="pale-orange color-block">
<div className="main-col">
<h3 className="h2">See how people are using our data</h3>
<p>
Find links to visualisations and analysis, art projects and applications
relating to the evolution of London, housing, energy, planning, heritage and
history&mdash;or something we havent imagined yet.
</p>
<img className="border-image" src="images/slide-6-showcase.png"
alt="Preview of data showcase page" />
</div>
</section>
</p>
<img className="border-image" src="images/slide-6-showcase.png"
alt="Preview of data showcase page" />
</div>
</section>
<section className="pale-green color-block">
<div className="main-col">
<h3 className="h2">Download, remix and reuse</h3>
<p>
<section className="pale-green color-block">
<div className="main-col">
<h3 className="h2">Download, remix and reuse</h3>
<p>
Access bulk downloads of data created through the project to use and reuse
under a liberal open data license. Let us know and well feature showcase
projects on the Colouring London site.
</p>
<img className="border-image" src="images/slide-5-download.png"
alt="Preview of download page" />
</div>
</section>
</p>
<img className="border-image" src="images/slide-5-download.png"
alt="Preview of download page" />
</div>
</section>
<div className="main-col">
<div className="main-col">
<form id="sign-up" action="https://tinyletter.com/colouringlondon" method="post"
target="popupwindow"
onSubmit={function() {window.open(
<form id="sign-up" action="https://tinyletter.com/colouringlondon" method="post"
target="popupwindow"
onSubmit={function() {window.open(
'https://tinyletter.com/colouringlondon',
'popupwindow',
'scrollbars=yes,width=800,height=600'); return true}}>
<h3 className="h1">Keep in touch</h3>
<p>
<h3 className="h1">Keep in touch</h3>
<p>
Receive occasional newsletters about the project as it develops. You can
read previous newsletters in our <a
href="https://tinyletter.com/colouringlondon/archive">newsletter archive</a>.
href="https://tinyletter.com/colouringlondon/archive">newsletter archive</a>.
</p>
<label htmlFor="tlemail">Enter your email address:</label>
<input className="form-control" type="email" name="email" id="tlemail" placeholder="name@example.com" />
<input type="hidden" value="1" name="embed"/>
<small className="form-text text-muted">
<a href="https://tinyletter.com">
</p>
<label htmlFor="tlemail">Enter your email address:</label>
<input className="form-control" type="email" name="email" id="tlemail" placeholder="name@example.com" />
<input type="hidden" value="1" name="embed"/>
<small className="form-text text-muted">
<a href="https://tinyletter.com">
powered by TinyLetter</a>.
We&rsquo;ll never share your email address.
</small>
<div className="buttons-container">
<input className="btn btn-outline-dark btn-block" type="submit" value="Sign up for updates" />
</small>
<div className="buttons-container">
<input className="btn btn-outline-dark btn-block" type="submit" value="Sign up for updates" />
</div>
</form>
</div>
</form>
</div>
</article>
);

View File

@ -59,11 +59,11 @@ class App extends React.Component {
}
selectBuilding(building) {
// get UPRNs and update
// get UPRNs and update
fetch(`/building/${building.building_id}/uprns.json`, {
method: 'GET',
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(
@ -84,7 +84,7 @@ class App extends React.Component {
fetch(`/building/${building.building_id}/like.json`, {
method: 'GET',
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(
@ -114,13 +114,13 @@ class App extends React.Component {
<Overview
{...props}
mode='view' user={this.state.user}
/>
/>
) } />
<Route exact path="/edit/:cat.html" render={(props) => (
<Overview
{...props}
mode='edit' user={this.state.user}
/>
/>
) } />
<Route exact path="/view/:cat/building/:building.html" render={(props) => (
<BuildingView
@ -128,7 +128,7 @@ class App extends React.Component {
{...this.state.building}
user={this.state.user}
building_like={this.state.building_like}
/>
/>
) } />
<Route exact path="/edit/:cat/building/:building.html" render={(props) => (
<BuildingEdit
@ -137,7 +137,7 @@ class App extends React.Component {
user={this.state.user}
building_like={this.state.building_like}
selectBuilding={this.selectBuilding}
/>
/>
) } />
</Switch>
<Switch>
@ -146,7 +146,7 @@ class App extends React.Component {
{...props}
building={this.state.building}
selectBuilding={this.selectBuilding}
/>
/>
) } />
<Route exact path="/about.html" component={AboutPage} />
<Route exact path="/login.html">
@ -160,7 +160,7 @@ class App extends React.Component {
user={this.state.user}
updateUser={this.updateUser}
logout={this.logout}
/>
/>
</Route>
<Route component={NotFound} />
</Switch>

View File

@ -5,13 +5,13 @@ import { MemoryRouter } from 'react-router-dom';
import App from './app';
describe('<App />', () => {
test('renders without exploding', () => {
const div = document.createElement('div');
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
});
test('renders without exploding', () => {
const div = document.createElement('div');
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
});
});

View File

@ -121,7 +121,7 @@ class EditForm extends Component {
fetch(`/building/${this.props.building_id}/like.json`, {
method: 'POST',
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({like: like})
@ -149,7 +149,7 @@ class EditForm extends Component {
method: 'POST',
body: JSON.stringify(this.state),
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(
@ -179,82 +179,82 @@ class EditForm extends Component {
<h3 className="h3">{this.props.title}</h3>
</NavLink>
<nav className="icon-buttons">
{
this.props.help?
<a className="icon-button help" title="Find out more" href={this.props.help}>
{
this.props.help?
<a className="icon-button help" title="Find out more" href={this.props.help}>
Info
</a>
: null
}
{
(match && !this.props.inactive && this.props.slug !== 'like')? // special-case for likes
<NavLink className="icon-button save" title="Save Changes"
onClick={this.handleSubmit}
to={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}>
</a>
: null
}
{
(match && !this.props.inactive && this.props.slug !== 'like')? // special-case for likes
<NavLink className="icon-button save" title="Save Changes"
onClick={this.handleSubmit}
to={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}>
Save
<SaveIcon />
</NavLink>
: null
}
<SaveIcon />
</NavLink>
: null
}
</nav>
</header>
{
match? (
!this.props.inactive?
<form action={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}
method="GET" onSubmit={this.handleSubmit}>
{
this.props.slug === 'location'?
<InfoBox msg="Text-based address fields are disabled at the moment. We're looking into how best to collect this data." />
: null
}
<ErrorBox msg={this.state.error} />
{
this.props.fields.map((props) => {
switch (props.type) {
case 'text':
return <TextInput {...props} handleChange={this.handleChange}
match? (
!this.props.inactive?
<form action={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}
method="GET" onSubmit={this.handleSubmit}>
{
this.props.slug === 'location'?
<InfoBox msg="Text-based address fields are disabled at the moment. We're looking into how best to collect this data." />
: null
}
<ErrorBox msg={this.state.error} />
{
this.props.fields.map((props) => {
switch (props.type) {
case 'text':
return <TextInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
case 'text_list':
return <TextListInput {...props} handleChange={this.handleChange}
case 'text_list':
return <TextListInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
case 'text_long':
return <LongTextInput {...props} handleChange={this.handleChange}
case 'text_long':
return <LongTextInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
case 'number':
return <NumberInput {...props} handleChange={this.handleChange}
case 'number':
return <NumberInput {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
case 'year_estimator':
return <YearEstimator {...props} handleChange={this.handleChange}
case 'year_estimator':
return <YearEstimator {...props} handleChange={this.handleChange}
value={this.state[props.slug]} key={props.slug} />
case 'text_multi':
return <MultiTextInput {...props} handleChange={this.handleUpdate}
case 'text_multi':
return <MultiTextInput {...props} handleChange={this.handleUpdate}
value={this.state[props.slug]} key={props.slug} />
case 'checkbox':
return <CheckboxInput {...props} handleChange={this.handleCheck}
case 'checkbox':
return <CheckboxInput {...props} handleChange={this.handleCheck}
value={this.state[props.slug]} key={props.slug} />
case 'like':
return <LikeButton {...props} handleLike={this.handleLike}
case 'like':
return <LikeButton {...props} handleLike={this.handleLike}
building_like={building_like}
value={this.state[props.slug]} key={props.slug} />
default:
return null
default:
return null
}
})
}
})
}
<InfoBox msg="Colouring may take a few seconds - try zooming the map or hitting refresh after saving (we're working on making this smoother)." />
<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>
}
</form>
: <form><InfoBox msg={`We're not collection data on ${this.props.title.toLowerCase()} yet - check back soon.`} /></form>
) : null
}
{
(this.props.slug === 'like')? // special-case for likes
null :
<div className="buttons-container">
<button type="submit" className="btn btn-primary">Save</button>
</div>
}
</form>
: <form><InfoBox msg={`We're not collection data on ${this.props.title.toLowerCase()} yet - check back soon.`} /></form>
) : null
}
</section>
)
}
@ -270,7 +270,7 @@ const TextInput = (props) => (
disabled={props.disabled}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
/>
</Fragment>
);
@ -327,30 +327,30 @@ class MultiTextInput extends Component {
render() {
const values = this.getValues();
return (
<Fragment>
<Label slug={this.props.slug} title={this.props.title} tooltip={this.props.tooltip} />
{
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}
<Fragment>
<Label slug={this.props.slug} title={this.props.title} tooltip={this.props.tooltip} />
{
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>
</Fragment>
)
}
}
@ -382,7 +382,7 @@ const NumberInput = (props) => (
value={props.value || ''}
disabled={props.disabled}
onChange={props.handleChange}
/>
/>
</Fragment>
);
@ -403,7 +403,7 @@ class YearEstimator extends Component {
render() {
return (
<NumberInput {...this.props} handleChange={this.props.handleChange}
value={this.props.value} key={this.props.slug} />
value={this.props.value} key={this.props.slug} />
)
}
}
@ -415,7 +415,7 @@ const CheckboxInput = (props) => (
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 }
@ -432,7 +432,7 @@ const LikeButton = (props) => (
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 }

View File

@ -35,31 +35,31 @@ const BuildingView = (props) => {
section_props.fields.map(field_props => {
switch (field_props.type) {
case 'uprn_list':
return <UPRNsDataEntry
key={field_props.slug}
title={field_props.title}
value={props.uprns}
tooltip={field_props.tooltip} />
case 'text_multi':
return <MultiDataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
tooltip={field_props.tooltip} />
case 'like':
return <LikeDataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
user_building_like={props.building_like}
tooltip={field_props.tooltip} />
default:
return <DataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
tooltip={field_props.tooltip} />
case 'uprn_list':
return <UPRNsDataEntry
key={field_props.slug}
title={field_props.title}
value={props.uprns}
tooltip={field_props.tooltip} />
case 'text_multi':
return <MultiDataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
tooltip={field_props.tooltip} />
case 'like':
return <LikeDataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
user_building_like={props.building_like}
tooltip={field_props.tooltip} />
default:
return <DataEntry
key={field_props.slug}
title={field_props.title}
value={props[field_props.slug]}
tooltip={field_props.tooltip} />
}
})
}
@ -84,30 +84,30 @@ const DataSection = (props) => {
<h3 className="h3">{props.title}</h3>
</NavLink>
<nav className="icon-buttons">
{
props.help?
<a className="icon-button help" title="Find out more" href={props.help}>
{
props.help?
<a className="icon-button help" title="Find out more" href={props.help}>
Info
</a>
: null
}
{
!props.inactive?
<NavLink className="icon-button edit" title="Edit data"
to={`/edit/${props.slug}/building/${props.building_id}.html`}>
</a>
: null
}
{
!props.inactive?
<NavLink className="icon-button edit" title="Edit data"
to={`/edit/${props.slug}/building/${props.building_id}.html`}>
Edit
<EditIcon />
</NavLink>
: null
}
<EditIcon />
</NavLink>
: null
}
</nav>
</header>
{
match?
!props.inactive?
<dl className="data-list">{props.children}</dl>
: <p className="data-intro">{props.intro}</p>
: null
<dl className="data-list">{props.children}</dl>
: <p className="data-intro">{props.intro}</p>
: null
}
</section>
);
@ -135,13 +135,13 @@ const LikeDataEntry = (props) => (
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</dt>
<dd>
{
(props.value != null)?
(props.value === 1)?
`${props.value} person likes this building`
: `${props.value} people like this building`
: '\u00A0'
}
{
(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>&hellip;including you!</dd> : null
@ -215,33 +215,33 @@ const UPRNsDataEntry = (props) => {
const with_parent = 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>{
no_parent.length?
no_parent.map(uprn => (
<li key={uprn.uprn}>{uprn.uprn}</li>
))
: '\u00A0'
}</Fragment>
{
with_parent.length?
<details>
<summary>Children</summary>
{
with_parent.map(uprn => (
<li key={uprn.uprn}>{uprn.uprn} (child of {uprn.parent_uprn})</li>
<Fragment>
<dt>
{ props.title }
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</dt>
<dd><ul className="uprn-list">
<Fragment>{
no_parent.length?
no_parent.map(uprn => (
<li key={uprn.uprn}>{uprn.uprn}</li>
))
}
</details>
: null
}
</ul></dd>
</Fragment>
: '\u00A0'
}</Fragment>
{
with_parent.length?
<details>
<summary>Children</summary>
{
with_parent.map(uprn => (
<li key={uprn.uprn}>{uprn.uprn} (child of {uprn.parent_uprn})</li>
))
}
</details>
: null
}
</ul></dd>
</Fragment>
)
}

View File

@ -8,15 +8,15 @@ function ErrorBox(props){
<Fragment>
{
(props.msg)?
(
<div className="alert alert-danger" role="alert">
{
(typeof props.msg === 'string' || props.msg instanceof String)?
props.msg
: 'Unexpected error'
}
</div>
) : null
(
<div className="alert alert-danger" role="alert">
{
(typeof props.msg === 'string' || props.msg instanceof String)?
props.msg
: 'Unexpected error'
}
</div>
) : null
}
</Fragment>
);

View File

@ -8,78 +8,96 @@ import './header.css';
* Render the main header using a responsive design
*/
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {collapseMenu: true};
this.handleClick = this.handleClick.bind(this);
}
constructor(props) {
super(props);
this.state = {collapseMenu: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
collapseMenu: !state.collapseMenu
}));
}
handleClick() {
this.setState(state => ({
collapseMenu: !state.collapseMenu
}));
}
render() {
return (
<header className="main-header">
<nav className="navbar navbar-light navbar-expand-md">
<span className="navbar-brand">
<Logo/>
</span>
<button className="navbar-toggler navbar-toggler-right" type="button"
onClick={this.handleClick} aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className={this.state.collapseMenu ? 'collapse navbar-collapse' : 'navbar-collapse'}>
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london">Hello</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/buildingcategories">Data Categories</a>
</li>
<li className="nav-item">
<NavLink to="/view/age.html" className="nav-link">View Maps</NavLink>
</li>
<li className="nav-item">
<NavLink to="/edit/age.html" className="nav-link">Add/Edit Data</NavLink>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/about">More about</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/whoisinvolved">Who&rsquo;s Involved?</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://discuss.colouring.london">Discuss</a>
</li>
{
this.props.user?
(
<li className="nav-item">
<NavLink to="/my-account.html" className="nav-link">
My account (Logged in as {this.props.user.username})
</NavLink>
</li>
):
(
<Fragment>
<li className="nav-item">
<NavLink to="/login.html" className="nav-link">Log in</NavLink>
</li>
<li className="nav-item">
<NavLink to="/sign-up.html" className="nav-link">Sign up</NavLink>
</li>
</Fragment>
)
}
</ul>
</div>
</nav>
</header>
);
}
render() {
return (
<header className="main-header">
<nav className="navbar navbar-light navbar-expand-md">
<span className="navbar-brand">
<Logo/>
</span>
<button className="navbar-toggler navbar-toggler-right" type="button"
onClick={this.handleClick} aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className={this.state.collapseMenu ? 'collapse navbar-collapse' : 'navbar-collapse'}>
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london">
Hello
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/buildingcategories">
Data Categories
</a>
</li>
<li className="nav-item">
<NavLink to="/view/age.html" className="nav-link">
View Maps
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/edit/age.html" className="nav-link">
Add/Edit Data
</NavLink>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/about">
More about
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://pages.colouring.london/whoisinvolved">
Who&rsquo;s Involved?
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="https://discuss.colouring.london">
Discuss
</a>
</li>
{
this.props.user?
(
<li className="nav-item">
<NavLink to="/my-account.html" className="nav-link">
My account (Logged in as {this.props.user.username})
</NavLink>
</li>
):
(
<Fragment>
<li className="nav-item">
<NavLink to="/login.html" className="nav-link">
Log in
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/sign-up.html" className="nav-link">
Sign up
</NavLink>
</li>
</Fragment>
)
}
</ul>
</div>
</nav>
</header>
);
}
}
export default Header;

View File

@ -5,7 +5,7 @@ import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faQuestionCircle, faPaintBrush, faInfoCircle, faTimes, faCheck, faCheckDouble,
faAngleLeft, faCaretDown } from '@fortawesome/free-solid-svg-icons'
faAngleLeft, faCaretDown } from '@fortawesome/free-solid-svg-icons'
library.add(
faQuestionCircle,

View File

@ -4,18 +4,18 @@ const InfoBox = (props) => (
<Fragment>
{
(props.msg || props.children)?
(
<div className="alert alert-info" role="alert">
{
(typeof props.msg === 'string' || props.msg instanceof String)?
props.msg
: 'Enjoy the colouring! Usual service should resume shortly.'
}
{
props.children
}
</div>
) : null
(
<div className="alert alert-info" role="alert">
{
(typeof props.msg === 'string' || props.msg instanceof String)?
props.msg
: 'Enjoy the colouring! Usual service should resume shortly.'
}
{
props.children
}
</div>
) : null
}
</Fragment>
);

View File

@ -18,46 +18,46 @@ const LEGEND_CONFIG = {
age: {
title: 'Age',
elements: [
{ color: '#f0eaba', text: '≥2000' },
{ color: '#fae269', text: '19802000' },
{ color: '#fbaf27', text: '19601980' },
{ color: '#e6711d', text: '19401960' },
{ color: '#d73d3a', text: '19201940' },
{ color: '#ba221c', text: '19001920' },
{ color: '#bb859b', text: '18801900' },
{ color: '#8b3654', text: '18601880' },
{ color: '#8f5385', text: '18401860' },
{ color: '#56619b', text: '18201840' },
{ color: '#6793b2', text: '18001820' },
{ color: '#83c3b3', text: '17801800' },
{ color: '#adc88f', text: '17601780' },
{ color: '#83a663', text: '17401760' },
{ color: '#77852d', text: '17201740' },
{ color: '#69814e', text: '17001720' },
{ color: '#d0c291', text: '16801700' },
{ color: '#918158', text: '16601680' },
{ color: '#7a5732', text: '<1660' },
{ color: '#f0eaba', text: '≥2000' },
{ color: '#fae269', text: '19802000' },
{ color: '#fbaf27', text: '19601980' },
{ color: '#e6711d', text: '19401960' },
{ color: '#d73d3a', text: '19201940' },
{ color: '#ba221c', text: '19001920' },
{ color: '#bb859b', text: '18801900' },
{ color: '#8b3654', text: '18601880' },
{ color: '#8f5385', text: '18401860' },
{ color: '#56619b', text: '18201840' },
{ color: '#6793b2', text: '18001820' },
{ color: '#83c3b3', text: '17801800' },
{ color: '#adc88f', text: '17601780' },
{ color: '#83a663', text: '17401760' },
{ color: '#77852d', text: '17201740' },
{ color: '#69814e', text: '17001720' },
{ color: '#d0c291', text: '16801700' },
{ color: '#918158', text: '16601680' },
{ color: '#7a5732', text: '<1660' },
]
},
size: {
title: 'Number of storeys',
elements: [
{ color: '#ffffcc', text: '≥40' },
{ color: '#fed976', text: '2039' },
{ color: '#fd8d3c', text: '1019' },
{ color: '#e31a1c', text: '69' },
{ color: '#800026', text: '15' },
{ color: '#ffffcc', text: '≥40' },
{ color: '#fed976', text: '2039' },
{ color: '#fd8d3c', text: '1019' },
{ color: '#e31a1c', text: '69' },
{ color: '#800026', text: '15' },
]
},
like: {
title: 'Like Me',
elements: [
{ color: '#bd0026', text: '👍👍👍 ≥10' },
{ color: '#e31a1c', text: '👍👍 510' },
{ color: '#fc4e2a', text: '👍 4' },
{ color: '#fd8d3c', text: '👍 3' },
{ color: '#feb24c', text: '👍 2' },
{ color: '#fed976', text: '👍 1' },
{ color: '#bd0026', text: '👍👍👍 ≥10' },
{ color: '#e31a1c', text: '👍👍 510' },
{ color: '#fc4e2a', text: '👍 4' },
{ color: '#fd8d3c', text: '👍 3' },
{ color: '#feb24c', text: '👍 2' },
{ color: '#fed976', text: '👍 1' },
]
},
use: {
@ -87,7 +87,7 @@ const LEGEND_CONFIG = {
planning: {
title: 'Planning',
elements: [
{ color: '#73ebaf', text: 'within conservation area' },
{ color: '#73ebaf', text: 'within conservation area' },
]
},
demolition: {
@ -136,14 +136,14 @@ const Legend = (props) => {
}
{
elements.length?
(<ul className="data-legend">
{
elements.map((data_item) => (
<LegendItem {...data_item} key={data_item.color} />
))
}
</ul>)
: (<p className="data-intro">Coming soon</p>)
(<ul className="data-legend">
{
elements.map((data_item) => (
<LegendItem {...data_item} key={data_item.color} />
))
}
</ul>)
: (<p className="data-intro">Coming soon</p>)
}
</div>
);

View File

@ -37,7 +37,7 @@ class Login extends Component {
method: 'POST',
body: JSON.stringify(this.state),
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(
@ -76,32 +76,32 @@ class Login extends Component {
<InfoBox msg="Welcome to Colouring London. You're one of the first people to use the site! ">
<br/>Please <a href="https://discuss.colouring.london/">discuss
suggestions for improvements</a> and <a
href="https://github.com/tomalrussell/colouring-london/issues">
href="https://github.com/tomalrussell/colouring-london/issues">
report issues or problems</a>.
</InfoBox>
<ErrorBox msg={this.state.error} />
<form onSubmit={this.handleSubmit}>
<label htmlFor="username">Username*</label>
<input name="username" id="username"
className="form-control" type="text"
value={this.state.username} onChange={this.handleChange}
placeholder="not-your-real-name" required
/>
className="form-control" type="text"
value={this.state.username} onChange={this.handleChange}
placeholder="not-your-real-name" required
/>
<label htmlFor="password">Password</label>
<input name="password" id="password"
className="form-control"
type={(this.state.show_password)? 'text': 'password'}
value={this.state.password} onChange={this.handleChange}
required
/>
className="form-control"
type={(this.state.show_password)? 'text': 'password'}
value={this.state.password} onChange={this.handleChange}
required
/>
<div className="form-check">
<input id="show_password" name="show_password"
className="form-check-input" type="checkbox"
checked={this.state.show_password}
onChange={this.handleChange}
/>
className="form-check-input" type="checkbox"
checked={this.state.show_password}
onChange={this.handleChange}
/>
<label htmlFor="show_password" className="form-check-label">Show password?</label>
</div>

View File

@ -126,7 +126,7 @@ class ColouringMap extends Component {
zoomControl={false}
attributionControl={false}
onClick={this.handleClick}
>
>
<TileLayer url={url} attribution={attribution} />
<TileLayer url={base_layer_url} minZoom={14} />
{ dataLayer }
@ -143,11 +143,11 @@ class ColouringMap extends Component {
}
{
this.props.match.url !== '/'? (
<Fragment>
<Legend slug={cat} />
<ThemeSwitcher onSubmit={this.themeSwitch} currentTheme={this.state.theme} />
<SearchBox onLocate={this.handleLocate} is_building={is_building} />
</Fragment>
<Fragment>
<Legend slug={cat} />
<ThemeSwitcher onSubmit={this.themeSwitch} currentTheme={this.state.theme} />
<SearchBox onLocate={this.handleLocate} is_building={is_building} />
</Fragment>
) : null
}
</Fragment>

View File

@ -63,7 +63,7 @@ class MyAccountPage extends Component {
Colouring London is under active development, Please <a href="https://discuss.colouring.london/">discuss
suggestions for improvements</a> and <a
href="https://github.com/tomalrussell/colouring-london/issues">
href="https://github.com/tomalrussell/colouring-london/issues">
report issues or problems</a>.
</p>

View File

@ -47,37 +47,37 @@ const OverviewSection = (props) => {
<nav className="icon-buttons">
{
props.help?
<a className="icon-button help" href={props.help}>
<a className="icon-button help" href={props.help}>
Info
</a>
: null
</a>
: null
}
{
props.mode === 'view'?
<NavLink className="icon-button edit" title="Edit data"
to={`/edit/${props.slug}.html`}>
<NavLink className="icon-button edit" title="Edit data"
to={`/edit/${props.slug}.html`}>
Edit
<EditIcon />
</NavLink>
: null
<EditIcon />
</NavLink>
: null
}
</nav>
</header>
{
(match && props.intro)?
(
<Fragment>
<p className="data-intro">{props.intro}</p>
<ul>
{
props.fields.map((field) => {
return (<li key={field.slug}>{field.title}</li>)
})
}
</ul>
</Fragment>
)
: null
(
<Fragment>
<p className="data-intro">{props.intro}</p>
<ul>
{
props.fields.map((field) => {
return (<li key={field.slug}>{field.title}</li>)
})
}
</ul>
</Fragment>
)
: null
}
</section>
)

View File

@ -28,7 +28,7 @@ class SearchBox extends Component {
});
// If the clear icon has been clicked, clear results list as well
if(e.target.value === '') {
this.clearResults();
this.clearResults();
}
}
@ -79,41 +79,41 @@ class SearchBox extends Component {
})
}
}).catch((err) => {
console.error(err)
console.error(err)
this.setState({
results: [],
fetching: false
})
this.setState({
results: [],
fetching: false
})
})
}
render() {
const resultsList = this.state.results.length?
<ul className="search-box-results">
{
this.state.results.map((result) => {
const label = result.attributes.label;
const lng = result.geometry.coordinates[0];
const lat = result.geometry.coordinates[1];
const zoom = result.attributes.zoom;
const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}`
return (
<li key={result.attributes.label}>
<a
className="search-box-result"
onClick={(e) => {
e.preventDefault();
this.props.onLocate(lat, lng, zoom);
}}
href={href}
{
this.state.results.map((result) => {
const label = result.attributes.label;
const lng = result.geometry.coordinates[0];
const lat = result.geometry.coordinates[1];
const zoom = result.attributes.zoom;
const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}`
return (
<li key={result.attributes.label}>
<a
className="search-box-result"
onClick={(e) => {
e.preventDefault();
this.props.onLocate(lat, lng, zoom);
}}
href={href}
>{`${label.substring(0,4)} ${label.substring(4, 7)}`}</a>
</li>
)
})
}
</li>
)
})
}
</ul>
: null;
: null;
return (
<div className={`search-box ${this.props.is_building? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
<form action="/search" method="GET" onSubmit={this.search}
@ -127,7 +127,7 @@ class SearchBox extends Component {
placeholder="Search for a postcode"
aria-label="Search for a postcode"
onChange={this.handleChange}
/>
/>
<button className="btn btn-outline-dark" type="submit">Search</button>
</form>
{ resultsList }

View File

@ -12,7 +12,7 @@ const Sidebar = (props) => (
<Link className="icon-button back" to={props.back}>
<BackIcon />
</Link>
: null
: null
}
<h2 className="h2">{props.title}</h2>
</header>

View File

@ -40,7 +40,7 @@ class SignUp extends Component {
method: 'POST',
body: JSON.stringify(this.state),
headers:{
'Content-Type': 'application/json'
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(
@ -75,7 +75,7 @@ class SignUp extends Component {
<InfoBox msg="Welcome to Colouring London. You're one of the first people to sign up! ">
<br/>Please <a href="https://discuss.colouring.london/">discuss
suggestions for improvements</a> and <a
href="https://github.com/tomalrussell/colouring-london/issues">
href="https://github.com/tomalrussell/colouring-london/issues">
report issues or problems</a>.
</InfoBox>
<p>
@ -85,38 +85,38 @@ class SignUp extends Component {
<form onSubmit={this.handleSubmit}>
<label htmlFor="username">Username*</label>
<input name="username" id="username"
className="form-control" type="text"
value={this.state.username} onChange={this.handleChange}
placeholder="not-your-real-name" required
/>
className="form-control" type="text"
value={this.state.username} onChange={this.handleChange}
placeholder="not-your-real-name" required
/>
<label htmlFor="email">Email (optional)</label>
<input name="email" id="email"
className="form-control" type="email"
value={this.state.email} onChange={this.handleChange}
placeholder="someone@example.com"
/>
className="form-control" type="email"
value={this.state.email} onChange={this.handleChange}
placeholder="someone@example.com"
/>
<label htmlFor="confirm_email">Confirm email (optional)</label>
<input name="confirm_email" id="confirm_email"
className="form-control" type="email"
value={this.state.confirm_email} onChange={this.handleChange}
/>
className="form-control" type="email"
value={this.state.confirm_email} onChange={this.handleChange}
/>
<label htmlFor="password">Password (at least 8 characters)</label>
<input name="password" id="password"
className="form-control"
type={(this.state.show_password)? 'text': 'password'}
value={this.state.password} onChange={this.handleChange}
required
/>
className="form-control"
type={(this.state.show_password)? 'text': 'password'}
value={this.state.password} onChange={this.handleChange}
required
/>
<div className="form-check">
<input id="show_password" name="show_password"
className="form-check-input" type="checkbox"
checked={this.state.show_password}
onChange={this.handleChange}
/>
className="form-check-input" type="checkbox"
checked={this.state.show_password}
onChange={this.handleChange}
/>
<label className="form-check-label" htmlFor="show_password">
Show password?
</label>
@ -124,14 +124,14 @@ class SignUp extends Component {
<div className="form-check">
<input id="confirm_conditions" name="confirm_conditions"
className="form-check-input" type="checkbox"
checked={this.state.confirm_conditions}
onChange={this.handleChange}
required />
className="form-check-input" type="checkbox"
checked={this.state.confirm_conditions}
onChange={this.handleChange}
required />
<label className="form-check-label" htmlFor="confirm_conditions">
I confirm that I have read and agree to the <a
href="/privacy-policy">privacy policy</a> and <a
href="/user-agreement">contributor agreement</a>.
href="/privacy-policy">privacy policy</a> and <a
href="/user-agreement">contributor agreement</a>.
</label>
</div>

View File

@ -5,7 +5,7 @@ import './theme-switcher.css';
const ThemeSwitcher = (props) => (
<form className={`theme-switcher ${props.currentTheme}`} onSubmit={props.onSubmit}>
<button className="btn btn-outline btn-outline-dark"
type="submit">
type="submit">
Switch theme ({(props.currentTheme === 'light')? 'Light' : 'Night'})
</button>
</form>

View File

@ -23,20 +23,20 @@ class Tooltip extends Component {
return (
<div className="tooltip-wrap">
<button className={(this.state.active? 'active ': '') + 'tooltip-hint icon-button'}
title={this.props.text}
onClick={this.handleClick}>
title={this.props.text}
onClick={this.handleClick}>
Hint
<InfoIcon />
</button>
{
this.state.active?
(
<div className="tooltip bs-tooltip-bottom">
<div className="arrow"></div>
<div className="tooltip-inner">{this.props.text}</div>
</div>
)
: null
(
<div className="tooltip bs-tooltip-bottom">
<div className="arrow"></div>
<div className="tooltip-inner">{this.props.text}</div>
</div>
)
: null
}
</div>
);

View File

@ -12,22 +12,22 @@ const server = http.createServer(app);
let currentApp = app;
server.listen(process.env.PORT || 3000, error => {
if (error) {
console.log(error);
}
if (error) {
console.log(error);
}
console.log('🚀 started');
console.log('🚀 started');
});
// In development mode, enable hot module reloading (HMR)
if (module.hot) {
console.log('✅ Server-side HMR Enabled!');
console.log('✅ Server-side HMR Enabled!');
module.hot.accept('./server', () => {
console.log('🔁 HMR Reloading x`./server`...');
server.removeListener('request', currentApp);
const newApp = require('./server').default;
server.on('request', newApp);
currentApp = newApp;
});
module.hot.accept('./server', () => {
console.log('🔁 HMR Reloading x`./server`...');
server.removeListener('request', currentApp);
const newApp = require('./server').default;
server.on('request', newApp);
currentApp = newApp;
});
}

View File

@ -141,15 +141,15 @@ function renderHTML(context, data, req, res) {
}
</style>
${
assets.client.css
? `<link rel="stylesheet" href="${assets.client.css}">`
: ''
}
assets.client.css
? `<link rel="stylesheet" href="${assets.client.css}">`
: ''
}
${
process.env.NODE_ENV === 'production'
? `<script src="${assets.client.js}" defer></script>`
: `<script src="${assets.client.js}" defer crossorigin></script>`
}
process.env.NODE_ENV === 'production'
? `<script src="${assets.client.js}" defer></script>`
: `<script src="${assets.client.js}" defer crossorigin></script>`
}
</head>
<body>
<div id="root">${markup}</div>

View File

@ -157,11 +157,11 @@ function cache_location(tileset, z, x, y) {
*/
function should_try_cache(tileset, z) {
if (tileset === 'date_year') {
// cache high zoom because of front page hits
// cache high zoom because of front page hits
return z <= 16
}
if (tileset === 'base_light' || tileset === 'base_night') {
// cache for higher zoom levels (unlikely to change)
// cache for higher zoom levels (unlikely to change)
return z <= 17
}
// else cache for lower zoom levels (change slowly)

View File

@ -138,7 +138,7 @@ function stitch_tile(tileset, z, x, y) {
const next_xy = get_xyz(bbox, next_z)
return Promise.all([
// recurse down through zoom levels, using cache if available...
// recurse down through zoom levels, using cache if available...
load_tile(tileset, next_z, next_xy.minX, next_xy.minY),
load_tile(tileset, next_z, next_xy.maxX, next_xy.minY),
load_tile(tileset, next_z, next_xy.minX, next_xy.maxY),
@ -149,9 +149,9 @@ function stitch_tile(tileset, z, x, y) {
bottom_left,
bottom_right
]) => {
// not possible to chain overlays in a single pipeline, but there may still be a better
// way to create image buffer here (four tiles resize to one at the next zoom level)
// instead of repeatedly creating `sharp` objects, to png, to buffer...
// not possible to chain overlays in a single pipeline, but there may still be a better
// way to create image buffer here (four tiles resize to one at the next zoom level)
// instead of repeatedly creating `sharp` objects, to png, to buffer...
return sharp({
create: {
width: TILE_SIZE * 2,