Sketch out new flow (with URL change)
This commit is contained in:
parent
09b59dbf6d
commit
377149975b
@ -10,12 +10,11 @@ import BuildingEdit from './building-edit';
|
||||
import BuildingView from './building-view';
|
||||
import ColouringMap from './map';
|
||||
import Header from './header';
|
||||
import Legend from './legend';
|
||||
import Overview from './overview';
|
||||
import Login from './login';
|
||||
import MyAccountPage from './my-account';
|
||||
import SignUp from './signup';
|
||||
import Welcome from './welcome';
|
||||
import BuildingEditAny from './building-edit-any';
|
||||
|
||||
|
||||
|
||||
@ -83,18 +82,26 @@ class App extends React.Component {
|
||||
<Route exact path="/">
|
||||
<Welcome />
|
||||
</Route>
|
||||
<Route exact path="/select.html">
|
||||
<BuildingEditAny user={this.state.user} />
|
||||
</Route>
|
||||
<Route exact path="/map/:map.html" component={Legend} />
|
||||
<Route exact path="/building/:building.html" render={(props) => (
|
||||
<Route exact path="/view/:cat.html" render={(props) => (
|
||||
<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
|
||||
{...props}
|
||||
{...this.state.building}
|
||||
user={this.state.user}
|
||||
/>
|
||||
) } />
|
||||
<Route exact path="/building/:building/edit.html" render={(props) => (
|
||||
<Route exact path="/edit/:cat/building/:building.html" render={(props) => (
|
||||
<BuildingEdit
|
||||
{...props}
|
||||
{...this.state.building}
|
||||
@ -106,7 +113,7 @@ class App extends React.Component {
|
||||
</CSSTransition>
|
||||
</TransitionGroup>
|
||||
<Switch>
|
||||
<Route exact path="/(select.html|map.*|building.*)?" render={(props) => (
|
||||
<Route exact path="/(edit.*|view.*)?" render={(props) => (
|
||||
<ColouringMap
|
||||
{...props}
|
||||
building={this.state.building}
|
||||
|
@ -1,17 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import Sidebar from './sidebar';
|
||||
import InfoBox from './info-box';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
const BuildingEditAny = (props) => {
|
||||
if (!props.user){
|
||||
return <Redirect to="/sign-up.html" />
|
||||
}
|
||||
return (
|
||||
<Sidebar title="Edit data">
|
||||
<InfoBox msg="Select a building to edit by clicking on the map…" />
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
export default BuildingEditAny;
|
@ -1,6 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link, NavLink, Redirect } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import ErrorBox from './error-box';
|
||||
import InfoBox from './info-box';
|
||||
@ -26,23 +25,32 @@ const BuildingEdit = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
const search = (props.location && props.location.search)?
|
||||
queryString.parse(props.location.search):
|
||||
{};
|
||||
const cat = get_cat(props.match.url);
|
||||
return (
|
||||
<Sidebar title={`You are editing`}
|
||||
back={search.cat? `/building/${props.building_id}.html?cat=${search.cat}`: `/building//${props.building_id}.html`}>
|
||||
<Sidebar
|
||||
key={props.building_id}
|
||||
title={`You are editing`}
|
||||
back={`/edit/${cat}.html`}>
|
||||
{
|
||||
CONFIG.map((conf_props) => {
|
||||
return <EditForm
|
||||
{...conf_props} {...props}
|
||||
search={search} key={conf_props.slug} />
|
||||
cat={cat} key={conf_props.slug} />
|
||||
})
|
||||
}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
function get_cat(url) {
|
||||
if (url === "/") {
|
||||
return "age"
|
||||
}
|
||||
const matches = /^\/(view|edit)\/([^\/.]+)/.exec(url);
|
||||
const cat = (matches && matches.length > 2)? matches[2] : "age";
|
||||
return cat;
|
||||
}
|
||||
|
||||
class EditForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -116,7 +124,7 @@ class EditForm extends Component {
|
||||
this.setState({error: res.error})
|
||||
} else {
|
||||
this.props.selectBuilding(res);
|
||||
const new_cat = this.props.search.cat;
|
||||
const new_cat = this.props.cat;
|
||||
this.props.history.push(`/building/${res.building_id}.html?cat=${new_cat}`);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
@ -125,14 +133,17 @@ class EditForm extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const match = this.props.search.cat === this.props.slug;
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
const match = this.props.cat === this.props.slug;
|
||||
return (
|
||||
<section className={(this.props.inactive)? "data-section inactive": "data-section"}>
|
||||
<header className={(match? "active " : "") + " section-header edit"}>
|
||||
<a><h3 className="h3">{this.props.title}</h3></a>
|
||||
<NavLink
|
||||
to={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}
|
||||
title={(this.props.inactive)? 'Coming soon… Click the ? for more info.' :
|
||||
(match)? 'Hide details' : 'Show details'}
|
||||
isActive={() => match}>
|
||||
<h3 className="h3">{this.props.title}</h3>
|
||||
</NavLink>
|
||||
<nav className="icon-buttons">
|
||||
{
|
||||
this.props.help?
|
||||
@ -142,31 +153,34 @@ class EditForm extends Component {
|
||||
: null
|
||||
}
|
||||
{
|
||||
(this.props.slug === 'like')? // special-case for likes
|
||||
(match && this.props.slug === 'like')? // special-case for likes
|
||||
<NavLink className="icon-button save" title="Done"
|
||||
to={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}>
|
||||
to={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}>
|
||||
Done
|
||||
<SaveIcon />
|
||||
</NavLink>
|
||||
:
|
||||
match? (
|
||||
<Fragment>
|
||||
<NavLink className="icon-button save" title="Save Changes"
|
||||
onClick={this.handleSubmit}
|
||||
to={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}>
|
||||
to={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}>
|
||||
Save
|
||||
<SaveIcon />
|
||||
</NavLink>
|
||||
<NavLink className="icon-button close-edit" title="Cancel"
|
||||
to={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}>
|
||||
to={`/view/${this.props.slug}/building/${this.props.building_id}.html`}>
|
||||
Cancel
|
||||
<CloseIcon />
|
||||
</NavLink>
|
||||
</Fragment>
|
||||
): null
|
||||
}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<form action={`/building/${this.props.building_id}.html?cat=${this.props.slug}`}
|
||||
{
|
||||
match? (
|
||||
<form action={`/edit/${this.props.slug}/building/${this.props.building_id}.html`}
|
||||
method="GET" onSubmit={this.handleSubmit}>
|
||||
<ErrorBox msg={this.state.error} />
|
||||
{
|
||||
@ -216,6 +230,8 @@ class EditForm extends Component {
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
) : null
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import Tooltip from './tooltip';
|
||||
@ -16,18 +15,18 @@ const BuildingView = (props) => {
|
||||
<Sidebar title="Building Not Found">
|
||||
<InfoBox msg="We can't find that one anywhere - try the map again?" />
|
||||
<div className="buttons-container with-space">
|
||||
<Link to="/map/age.html" className="btn btn-secondary">Back to maps</Link>
|
||||
<Link to="/view/age.html" className="btn btn-secondary">Back to maps</Link>
|
||||
</div>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
const search = (props.location && props.location.search)? queryString.parse(props.location.search): {};
|
||||
const cat = get_cat(props.match.url);
|
||||
return (
|
||||
<Sidebar title={`Data available for this building`} back={search.cat? `/map/${search.cat}.html` : "/map/age.html"}>
|
||||
<Sidebar title={`Data available for this building`} back={`/view/${cat}.html`}>
|
||||
{
|
||||
CONFIG.map(section_props => (
|
||||
<DataSection
|
||||
key={section_props.slug} search={search}
|
||||
key={section_props.slug} cat={cat}
|
||||
building_id={props.building_id}
|
||||
{...section_props}>
|
||||
{
|
||||
@ -54,13 +53,23 @@ const BuildingView = (props) => {
|
||||
}
|
||||
|
||||
|
||||
function get_cat(url) {
|
||||
if (url === "/") {
|
||||
return "age"
|
||||
}
|
||||
const matches = /^\/(view|edit)\/([^\/.]+)/.exec(url);
|
||||
const cat = (matches && matches.length > 2)? matches[2] : "age";
|
||||
return cat;
|
||||
}
|
||||
|
||||
|
||||
const DataSection = (props) => {
|
||||
const match = props.search.cat === props.slug;
|
||||
const match = props.cat === props.slug;
|
||||
return (
|
||||
<section id={props.slug} className={(props.inactive)? "data-section inactive": "data-section"}>
|
||||
<header className={(match? "active " : "") + " section-header view"}>
|
||||
<NavLink
|
||||
to={`/building/${props.building_id}.html` + ((match)? '': `?cat=${props.slug}`)}
|
||||
to={`/view/${props.slug}/building/${props.building_id}.html`}
|
||||
title={(props.inactive)? 'Coming soon… Click the ? for more info.' :
|
||||
(match)? 'Hide details' : 'Show details'}
|
||||
isActive={() => match}>
|
||||
@ -77,7 +86,7 @@ const DataSection = (props) => {
|
||||
{
|
||||
!props.inactive?
|
||||
<NavLink className="icon-button edit" title="Edit data"
|
||||
to={`/building/${props.building_id}/edit.html?cat=${props.slug}`}>
|
||||
to={`/edit/${props.slug}/building/${props.building_id}.html`}>
|
||||
Edit
|
||||
<EditIcon />
|
||||
</NavLink>
|
||||
|
@ -19,10 +19,10 @@ const Header = (props) => (
|
||||
<a className="nav-link" href="https://pages.colouring.london/about">More about</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/map/age.html" className="nav-link">View Maps</NavLink>
|
||||
<NavLink to="/view/age.html" className="nav-link">View Maps</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/select.html" className="nav-link">Add/Edit Data</NavLink>
|
||||
<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/buildingcategories">Building Categories</a>
|
||||
|
@ -1,33 +1,17 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import { EditIcon } from './icons';
|
||||
import './legend.css';
|
||||
|
||||
import CONFIG from './fields-config.json';
|
||||
import InfoBox from './info-box';
|
||||
|
||||
|
||||
const LEGEND_CONFIG = {
|
||||
location: [
|
||||
{
|
||||
title: 'Location Information (number of data entries)',
|
||||
slug: 'location',
|
||||
elements: [
|
||||
{ color: '#f0f9e8', text: '>5' },
|
||||
{ color: '#bae4bc', text: '4' },
|
||||
{ color: '#7bccc4', text: '3' },
|
||||
{ color: '#43a2ca', text: '2' },
|
||||
{ color: '#0868ac', text: '1' }
|
||||
]
|
||||
}
|
||||
],
|
||||
age: [
|
||||
{
|
||||
title: 'Year Built',
|
||||
slug: 'date_year',
|
||||
elements: [
|
||||
{ color: '#f0eaba', text: '≥2000' },
|
||||
{ color: '#fae269', text: '1980–2000' },
|
||||
{ color: '#fbaf27', text: '1960–1980' },
|
||||
@ -47,14 +31,8 @@ const LEGEND_CONFIG = {
|
||||
{ color: '#d0c291', text: '1680–1700' },
|
||||
{ color: '#918158', text: '1660–1680' },
|
||||
{ color: '#7a5732', text: '<1660' },
|
||||
]
|
||||
}
|
||||
],
|
||||
size: [
|
||||
{
|
||||
title: 'Number of storeys',
|
||||
slug: 'size_storeys',
|
||||
elements: [
|
||||
{ color: '#ffffcc', text: '≥20' },
|
||||
{ color: '#ffeda0', text: '15-20' },
|
||||
{ color: '#fed976', text: '10–15' },
|
||||
@ -64,99 +42,31 @@ const LEGEND_CONFIG = {
|
||||
{ color: '#e31a1c', text: '3' },
|
||||
{ color: '#bd0026', text: '2' },
|
||||
{ color: '#800026', text: '1' },
|
||||
]
|
||||
}
|
||||
],
|
||||
like: [
|
||||
{
|
||||
title: 'Which buildings do you like?',
|
||||
slug: 'like',
|
||||
elements: [
|
||||
{ color: '#f65400', text: 'We like these buildings 👍 🎉 +1' },
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
const Legend = (props) => {
|
||||
var data_layer = undefined;
|
||||
if (props.match && props.match.params && props.match.params.map) {
|
||||
data_layer = props.match.params.map;
|
||||
}
|
||||
|
||||
let elements = LEGEND_CONFIG[props.slug];
|
||||
return (
|
||||
<Sidebar title="View Maps">
|
||||
<InfoBox msg="Click on the map to see more information about a building…" />
|
||||
{
|
||||
CONFIG.map((data_group) => (
|
||||
<LegendGroup {...data_group} maps={LEGEND_CONFIG[data_group.slug]}
|
||||
data_layer={data_layer} key={data_group.slug} />
|
||||
))
|
||||
}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
const LegendGroup = (props) => {
|
||||
const match = props.data_layer === props.slug;
|
||||
const inactive = props.inactive || !props.maps;
|
||||
return (
|
||||
<section className={(inactive? "inactive ": "") + "data-section legend"}>
|
||||
<header className={(match? "active " : "") + " section-header view"}>
|
||||
<NavLink
|
||||
to={match? "/map/base.html": `/map/${props.slug}.html`}
|
||||
isActive={() => match}
|
||||
title={(inactive)? 'Coming soon… Click the ? for more info.' :
|
||||
(match)? '' : 'Show on map'}>
|
||||
<h3 className="h3">{props.title}</h3>
|
||||
</NavLink>
|
||||
<nav className="icon-buttons">
|
||||
{
|
||||
props.help?
|
||||
<a className="icon-button help" href={props.help}>
|
||||
More info
|
||||
</a>
|
||||
: null
|
||||
}
|
||||
<NavLink className="icon-button edit" title="Edit data"
|
||||
to={`/select.html?cat=${props.slug}`}>
|
||||
Edit
|
||||
<EditIcon />
|
||||
</NavLink>
|
||||
</nav>
|
||||
</header>
|
||||
<dl className="data-list">
|
||||
{
|
||||
(match && props.maps)?
|
||||
props.maps.map((data_section) => (
|
||||
<LegendSection {...data_section} key={data_section.slug}>
|
||||
{
|
||||
data_section.elements.map((data_item) => (
|
||||
<LegendItem {...data_item} key={data_item.color} />
|
||||
))
|
||||
}
|
||||
</LegendSection>
|
||||
))
|
||||
: null
|
||||
}
|
||||
</dl>
|
||||
</section>
|
||||
)
|
||||
};
|
||||
|
||||
const LegendSection = (props) => (
|
||||
<Fragment>
|
||||
<dt>
|
||||
{ props.title }
|
||||
</dt>
|
||||
<dd>
|
||||
<ul className="data-legend">
|
||||
{ props.children }
|
||||
{
|
||||
elements.map((data_item) => (
|
||||
<LegendItem {...data_item} key={data_item.color} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</dd>
|
||||
</Fragment>
|
||||
);
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
const LegendItem = (props) => (
|
||||
<li>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Map, TileLayer, ZoomControl, AttributionControl } from 'react-leaflet-universal';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import '../../node_modules/leaflet/dist/leaflet.css'
|
||||
import './map.css'
|
||||
@ -26,14 +25,12 @@ class ColouringMap extends Component {
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
if (this.props.match.url.match('edit')){
|
||||
// don't navigate away from edit view
|
||||
return
|
||||
}
|
||||
var lat = e.latlng.lat
|
||||
var lng = e.latlng.lng
|
||||
const is_building = /building/.test(this.props.match.url);
|
||||
const new_cat = get_cat(is_building, this.props.location, this.props.match.url);
|
||||
const is_edit = this.props.match.url.match('edit')
|
||||
const mode = is_edit? 'edit': 'view';
|
||||
const lat = e.latlng.lat
|
||||
const lng = e.latlng.lng
|
||||
const new_cat = get_cat(this.props.match.url);
|
||||
const map_cat = new_cat || 'age';
|
||||
fetch(
|
||||
'/buildings/locate?lat='+lat+'&lng='+lng
|
||||
).then(
|
||||
@ -42,12 +39,11 @@ class ColouringMap extends Component {
|
||||
if (data && data.length){
|
||||
const building = data[0];
|
||||
this.props.selectBuilding(building);
|
||||
this.props.history.push(`/building/${building.building_id}.html`);
|
||||
this.props.history.push(`/${mode}/${map_cat}/building/${building.building_id}.html`);
|
||||
} else {
|
||||
// deselect but keep/return to expected colour theme
|
||||
this.props.selectBuilding(undefined);
|
||||
const map_cat = new_cat || 'age';
|
||||
this.props.history.push(`/map/${map_cat}.html`);
|
||||
this.props.history.push(`/${mode}/${map_cat}.html`);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
(err) => console.error(err)
|
||||
@ -72,7 +68,7 @@ class ColouringMap extends Component {
|
||||
|
||||
// colour-data tiles
|
||||
const is_building = /building/.test(this.props.match.url);
|
||||
const cat = get_cat(is_building, this.props.location, this.props.match.url);
|
||||
const cat = get_cat(this.props.match.url);
|
||||
const tileset_by_cat = {
|
||||
age: 'date_year',
|
||||
size: 'size_storeys',
|
||||
@ -122,18 +118,12 @@ class ColouringMap extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
function get_cat(is_building, location, url) {
|
||||
function get_cat(url) {
|
||||
if (url === "/") {
|
||||
return "age"
|
||||
}
|
||||
const search = (location && location.search)? queryString.parse(location.search) : {};
|
||||
var cat, matches;
|
||||
if (is_building) {
|
||||
cat = search.cat;
|
||||
} else {
|
||||
matches = /\/map\/([^.]+).html/.exec(url);
|
||||
cat = (matches && matches.length > 1)? matches[1] : "";
|
||||
}
|
||||
const matches = /^\/(view|edit)\/([^\/.]+)/.exec(url);
|
||||
const cat = (matches && matches.length > 2)? matches[2] : "age";
|
||||
return cat;
|
||||
}
|
||||
|
||||
|
68
app/src/frontend/overview.js
Normal file
68
app/src/frontend/overview.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { NavLink, Redirect } from 'react-router-dom';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import { EditIcon } from './icons';
|
||||
import CONFIG from './fields-config.json';
|
||||
|
||||
const Overview = (props) => {
|
||||
var data_layer = 'age'; // always default
|
||||
if (props.match && props.match.params && props.match.params.cat) {
|
||||
data_layer = props.match.params.cat;
|
||||
}
|
||||
|
||||
if (props.mode === 'edit' && !props.user){
|
||||
return <Redirect to="/sign-up.html" />
|
||||
}
|
||||
|
||||
let title = (props.mode === 'view')? 'View maps' : 'Add or edit data';
|
||||
|
||||
return (
|
||||
<Sidebar title={title}>
|
||||
{
|
||||
CONFIG.map((data_group) => (
|
||||
<OverviewSection {...data_group}
|
||||
data_layer={data_layer} key={data_group.slug} mode={props.mode} />
|
||||
))
|
||||
}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
const OverviewSection = (props) => {
|
||||
const match = props.data_layer === props.slug;
|
||||
const inactive = props.inactive;
|
||||
return (
|
||||
<section className={(inactive? "inactive ": "") + "data-section legend"}>
|
||||
<header className={(match? "active " : "") + " section-header view"}>
|
||||
<NavLink
|
||||
to={`/${props.mode}/${props.slug}.html`}
|
||||
isActive={() => match}
|
||||
title={(inactive)? 'Coming soon… Click the ? for more info.' :
|
||||
(match)? '' : 'Show on map'}>
|
||||
<h3 className="h3">{props.title}</h3>
|
||||
</NavLink>
|
||||
<nav className="icon-buttons">
|
||||
{
|
||||
props.help?
|
||||
<a className="icon-button help" href={props.help}>
|
||||
More info
|
||||
</a>
|
||||
: null
|
||||
}
|
||||
{
|
||||
props.mode === 'view'?
|
||||
<NavLink className="icon-button edit" title="Edit data"
|
||||
to={`/edit/${props.slug}.html`}>
|
||||
Edit
|
||||
<EditIcon />
|
||||
</NavLink>
|
||||
: null
|
||||
}
|
||||
</nav>
|
||||
</header>
|
||||
</section>
|
||||
)
|
||||
};
|
||||
|
||||
export default Overview;
|
@ -15,7 +15,7 @@ function strictParseInt(value) {
|
||||
|
||||
|
||||
function parseBuildingURL(url){
|
||||
const re = /^\/building\/([^\/]+)(\/edit)?.html/;
|
||||
const re = /\/building\/([^\/]+).html/;
|
||||
const matches = re.exec(url);
|
||||
|
||||
if (matches && matches.length >= 2) {
|
||||
|
Loading…
Reference in New Issue
Block a user