Lint prop-types, camelCase
This commit is contained in:
parent
aef53a0ae0
commit
9b96872922
@ -1,13 +1,15 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
@ -23,9 +25,12 @@
|
||||
"curly": "warn",
|
||||
"quotes": ["warn", "single"],
|
||||
"indent": ["warn", 4],
|
||||
"no-multi-spaces": ["error", {"ignoreEOLComments": true} ]
|
||||
"no-multi-spaces": ["warn", {"ignoreEOLComments": true} ],
|
||||
"comma-spacing": ["warn"],
|
||||
"camelcase": ["warn", {"ignoreDestructuring": true ,"properties": "never"}]
|
||||
},
|
||||
"plugins": [
|
||||
"jest",
|
||||
"react"
|
||||
],
|
||||
"settings": {
|
||||
|
20
app/package-lock.json
generated
20
app/package-lock.json
generated
@ -4904,6 +4904,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-jest": {
|
||||
"version": "22.6.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.6.4.tgz",
|
||||
"integrity": "sha512-36OqnZR/uMCDxXGmTsqU4RwllR0IiB/XF8GW3ODmhsjiITKuI0GpgultWFt193ipN3HARkaIcKowpE6HBvRHNg==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz",
|
||||
@ -12903,12 +12909,13 @@
|
||||
}
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
|
||||
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
},
|
||||
"proxy-addr": {
|
||||
@ -13412,8 +13419,7 @@
|
||||
"react-is": {
|
||||
"version": "16.8.6",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
|
||||
},
|
||||
"react-leaflet": {
|
||||
"version": "1.9.1",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"mapnik": "^4.2.1",
|
||||
"node-fs": "^0.1.7",
|
||||
"pg-promise": "^8.7.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-leaflet": "^1.0.1",
|
||||
@ -36,6 +37,7 @@
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-jest": "^22.6.4",
|
||||
"eslint-plugin-react": "^7.13.0",
|
||||
"razzle": "^3.0.0"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
*/
|
||||
import db from '../db';
|
||||
import { remove_all_at_bbox } from '../tiles/cache';
|
||||
import { removeAllAtBbox } from '../tiles/cache';
|
||||
|
||||
// data type note: PostgreSQL bigint (64-bit) is handled as string in JavaScript, because of
|
||||
// JavaScript numerics are 64-bit double, giving only partial coverage.
|
||||
@ -85,10 +85,10 @@ function getBuildingById(id) {
|
||||
});
|
||||
}
|
||||
|
||||
function getBuildingLikeById(building_id, user_id) {
|
||||
function getBuildingLikeById(buildingId, userId) {
|
||||
return db.oneOrNone(
|
||||
'SELECT true as like FROM building_user_likes WHERE building_id = $1 and user_id = $2 LIMIT 1',
|
||||
[building_id, user_id]
|
||||
[buildingId, userId]
|
||||
).then(res => {
|
||||
return res && res.like
|
||||
}).catch(function (error) {
|
||||
@ -107,13 +107,7 @@ function getBuildingUPRNsById(id) {
|
||||
});
|
||||
}
|
||||
|
||||
function saveBuilding(building_id, building, user_id) {
|
||||
// save building could fail if the revision seen by the user != the latest revision
|
||||
// - any 'intuitive' retries would need to be handled by clients of this code
|
||||
// revision id allows for a long user 'think time' between view-building, update-building
|
||||
// (optimistic locking implemented using field-based row versioning)
|
||||
// const previous_revision_id = building.revision_id;
|
||||
|
||||
function saveBuilding(buildingId, building, userId) {
|
||||
// remove read-only fields from consideration
|
||||
delete building.building_id;
|
||||
delete building.revision_id;
|
||||
@ -127,10 +121,10 @@ function saveBuilding(building_id, building, user_id) {
|
||||
return db.tx(t => {
|
||||
return t.one(
|
||||
'SELECT * FROM buildings WHERE building_id = $1 FOR UPDATE;',
|
||||
[building_id]
|
||||
).then(old_building => {
|
||||
const patches = compare(old_building, building, BUILDING_FIELD_WHITELIST);
|
||||
console.log('Patching', building_id, patches)
|
||||
[buildingId]
|
||||
).then(oldBuilding => {
|
||||
const patches = compare(oldBuilding, building, BUILDING_FIELD_WHITELIST);
|
||||
console.log('Patching', buildingId, patches)
|
||||
const forward = patches[0];
|
||||
const reverse = patches[1];
|
||||
if (Object.keys(forward).length === 0) {
|
||||
@ -143,10 +137,10 @@ function saveBuilding(building_id, building, user_id) {
|
||||
$1:json, $2:json, $3, $4
|
||||
) RETURNING log_id
|
||||
`,
|
||||
[forward, reverse, building_id, user_id]
|
||||
[forward, reverse, buildingId, userId]
|
||||
).then(revision => {
|
||||
const sets = db.$config.pgp.helpers.sets(forward);
|
||||
console.log('Setting', building_id, sets)
|
||||
console.log('Setting', buildingId, sets)
|
||||
return t.one(
|
||||
`UPDATE
|
||||
buildings
|
||||
@ -158,9 +152,9 @@ function saveBuilding(building_id, building, user_id) {
|
||||
RETURNING
|
||||
*
|
||||
`,
|
||||
[revision.log_id, sets, building_id]
|
||||
[revision.log_id, sets, buildingId]
|
||||
).then((data) => {
|
||||
expireBuildingTileCache(building_id)
|
||||
expireBuildingTileCache(buildingId)
|
||||
return data
|
||||
})
|
||||
});
|
||||
@ -172,7 +166,7 @@ function saveBuilding(building_id, building, user_id) {
|
||||
}
|
||||
|
||||
|
||||
function likeBuilding(building_id, user_id) {
|
||||
function likeBuilding(buildingId, userId) {
|
||||
// start transaction around save operation
|
||||
// - insert building-user like
|
||||
// - count total likes
|
||||
@ -182,11 +176,11 @@ function likeBuilding(building_id, user_id) {
|
||||
return db.tx({ serializable }, t => {
|
||||
return t.none(
|
||||
'INSERT INTO building_user_likes ( building_id, user_id ) VALUES ($1, $2);',
|
||||
[building_id, user_id]
|
||||
[buildingId, userId]
|
||||
).then(() => {
|
||||
return t.one(
|
||||
'SELECT count(*) as likes FROM building_user_likes WHERE building_id = $1;',
|
||||
[building_id]
|
||||
[buildingId]
|
||||
).then(building => {
|
||||
return t.one(
|
||||
`INSERT INTO logs (
|
||||
@ -195,7 +189,7 @@ function likeBuilding(building_id, user_id) {
|
||||
$1:json, $2, $3
|
||||
) RETURNING log_id
|
||||
`,
|
||||
[{ likes_total: building.likes }, building_id, user_id]
|
||||
[{ likes_total: building.likes }, buildingId, userId]
|
||||
).then(revision => {
|
||||
return t.one(
|
||||
`UPDATE buildings
|
||||
@ -207,9 +201,9 @@ function likeBuilding(building_id, user_id) {
|
||||
RETURNING
|
||||
*
|
||||
`,
|
||||
[revision.log_id, building.likes, building_id]
|
||||
[revision.log_id, building.likes, buildingId]
|
||||
).then((data) => {
|
||||
expireBuildingTileCache(building_id)
|
||||
expireBuildingTileCache(buildingId)
|
||||
return data
|
||||
})
|
||||
})
|
||||
@ -227,7 +221,7 @@ function likeBuilding(building_id, user_id) {
|
||||
}
|
||||
|
||||
|
||||
function unlikeBuilding(building_id, user_id) {
|
||||
function unlikeBuilding(buildingId, userId) {
|
||||
// start transaction around save operation
|
||||
// - insert building-user like
|
||||
// - count total likes
|
||||
@ -237,11 +231,11 @@ function unlikeBuilding(building_id, user_id) {
|
||||
return db.tx({ serializable }, t => {
|
||||
return t.none(
|
||||
'DELETE FROM building_user_likes WHERE building_id = $1 AND user_id = $2;',
|
||||
[building_id, user_id]
|
||||
[buildingId, userId]
|
||||
).then(() => {
|
||||
return t.one(
|
||||
'SELECT count(*) as likes FROM building_user_likes WHERE building_id = $1;',
|
||||
[building_id]
|
||||
[buildingId]
|
||||
).then(building => {
|
||||
return t.one(
|
||||
`INSERT INTO logs (
|
||||
@ -250,7 +244,7 @@ function unlikeBuilding(building_id, user_id) {
|
||||
$1:json, $2, $3
|
||||
) RETURNING log_id
|
||||
`,
|
||||
[{ likes_total: building.likes }, building_id, user_id]
|
||||
[{ likes_total: building.likes }, buildingId, userId]
|
||||
).then(revision => {
|
||||
return t.one(
|
||||
`UPDATE buildings
|
||||
@ -262,9 +256,9 @@ function unlikeBuilding(building_id, user_id) {
|
||||
RETURNING
|
||||
*
|
||||
`,
|
||||
[revision.log_id, building.likes, building_id]
|
||||
[revision.log_id, building.likes, buildingId]
|
||||
).then((data) => {
|
||||
expireBuildingTileCache(building_id)
|
||||
expireBuildingTileCache(buildingId)
|
||||
return data
|
||||
})
|
||||
})
|
||||
@ -281,7 +275,7 @@ function unlikeBuilding(building_id, user_id) {
|
||||
});
|
||||
}
|
||||
|
||||
function privateQueryBuildingBBOX(building_id){
|
||||
function privateQueryBuildingBBOX(buildingId){
|
||||
return db.one(
|
||||
`SELECT
|
||||
ST_XMin(envelope) as xmin,
|
||||
@ -297,14 +291,14 @@ function privateQueryBuildingBBOX(building_id){
|
||||
AND
|
||||
b.building_id = $1
|
||||
) as envelope`,
|
||||
[building_id]
|
||||
[buildingId]
|
||||
)
|
||||
}
|
||||
|
||||
function expireBuildingTileCache(building_id) {
|
||||
privateQueryBuildingBBOX(building_id).then((bbox) => {
|
||||
const building_bbox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin]
|
||||
remove_all_at_bbox(building_bbox);
|
||||
function expireBuildingTileCache(buildingId) {
|
||||
privateQueryBuildingBBOX(buildingId).then((bbox) => {
|
||||
const buildingBbox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin]
|
||||
removeAllAtBbox(buildingBbox);
|
||||
})
|
||||
}
|
||||
|
||||
@ -361,21 +355,21 @@ const BUILDING_FIELD_WHITELIST = new Set([
|
||||
* - forward patch is object with {keys: new_values}
|
||||
* - reverse patch is object with {keys: old_values}
|
||||
*
|
||||
* @param {object} old_obj
|
||||
* @param {object} new_obj
|
||||
* @param {object} oldObj
|
||||
* @param {object} newObj
|
||||
* @param {Set} whitelist
|
||||
* @returns {[object, object]}
|
||||
*/
|
||||
function compare(old_obj, new_obj, whitelist) {
|
||||
const reverse_patch = {}
|
||||
const forward_patch = {}
|
||||
for (const [key, value] of Object.entries(new_obj)) {
|
||||
if (old_obj[key] !== value && whitelist.has(key)) {
|
||||
reverse_patch[key] = old_obj[key];
|
||||
forward_patch[key] = value;
|
||||
function compare(oldObj, newObj, whitelist) {
|
||||
const reverse = {}
|
||||
const forward = {}
|
||||
for (const [key, value] of Object.entries(newObj)) {
|
||||
if (oldObj[key] !== value && whitelist.has(key)) {
|
||||
reverse[key] = oldObj[key];
|
||||
forward[key] = value;
|
||||
}
|
||||
}
|
||||
return [forward_patch, reverse_patch]
|
||||
return [forward, reverse]
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -9,7 +9,7 @@
|
||||
import db from '../db';
|
||||
|
||||
function queryLocation(term) {
|
||||
const max_results = 5;
|
||||
const limit = 5;
|
||||
return db.manyOrNone(
|
||||
`SELECT
|
||||
search_str, search_class, ST_AsGeoJSON(center), zoom,
|
||||
@ -19,7 +19,7 @@ function queryLocation(term) {
|
||||
ORDER BY
|
||||
dist
|
||||
LIMIT $2;`,
|
||||
[term, max_results]
|
||||
[term, limit]
|
||||
).catch((error) => {
|
||||
console.error(error);
|
||||
return undefined;
|
||||
|
@ -70,7 +70,7 @@ function authUser(username, password) {
|
||||
})
|
||||
}
|
||||
|
||||
function getUserById(user_id) {
|
||||
function getUserById(id) {
|
||||
return db.one(
|
||||
`SELECT
|
||||
username, email, registered, api_key
|
||||
@ -79,7 +79,7 @@ function getUserById(user_id) {
|
||||
WHERE
|
||||
user_id = $1
|
||||
`, [
|
||||
user_id
|
||||
id
|
||||
]
|
||||
).catch(function (error) {
|
||||
console.error('Error:', error)
|
||||
@ -87,7 +87,7 @@ function getUserById(user_id) {
|
||||
});
|
||||
}
|
||||
|
||||
function getNewUserAPIKey(user_id) {
|
||||
function getNewUserAPIKey(id) {
|
||||
return db.one(
|
||||
`UPDATE
|
||||
users
|
||||
@ -98,7 +98,7 @@ function getNewUserAPIKey(user_id) {
|
||||
RETURNING
|
||||
api_key
|
||||
`, [
|
||||
user_id
|
||||
id
|
||||
]
|
||||
).catch(function (error) {
|
||||
console.error('Error:', error)
|
||||
@ -106,7 +106,7 @@ function getNewUserAPIKey(user_id) {
|
||||
});
|
||||
}
|
||||
|
||||
function authAPIUser(api_key) {
|
||||
function authAPIUser(key) {
|
||||
return db.one(
|
||||
`SELECT
|
||||
user_id
|
||||
@ -115,7 +115,7 @@ function authAPIUser(api_key) {
|
||||
WHERE
|
||||
api_key = $1
|
||||
`, [
|
||||
api_key
|
||||
key
|
||||
]
|
||||
).catch(function (error) {
|
||||
console.error('Error:', error)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Route, Switch, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
||||
import './app.css';
|
||||
@ -170,6 +171,12 @@ class App extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
user: PropTypes.object,
|
||||
building: PropTypes.object,
|
||||
building_like: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to fall back on in case of 404 or no other match
|
||||
*/
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link, NavLink, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from './error-box';
|
||||
import InfoBox from './info-box';
|
||||
@ -32,16 +33,22 @@ const BuildingEdit = (props) => {
|
||||
title={'You are editing'}
|
||||
back={`/edit/${cat}.html`}>
|
||||
{
|
||||
CONFIG.map((conf_props) => {
|
||||
CONFIG.map((section) => {
|
||||
return <EditForm
|
||||
{...conf_props} {...props}
|
||||
cat={cat} key={conf_props.slug} />
|
||||
{...section} {...props}
|
||||
cat={cat} key={section.slug} />
|
||||
})
|
||||
}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
BuildingEdit.propTypes = {
|
||||
user: PropTypes.object,
|
||||
match: PropTypes.object,
|
||||
building_id: PropTypes.string,
|
||||
}
|
||||
|
||||
class EditForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -167,7 +174,7 @@ class EditForm extends Component {
|
||||
|
||||
render() {
|
||||
const match = this.props.cat === this.props.slug;
|
||||
const building_like = this.props.building_like;
|
||||
const buildingLike = this.props.building_like;
|
||||
return (
|
||||
<section className={(this.props.inactive)? 'data-section inactive': 'data-section'}>
|
||||
<header className={`section-header edit ${this.props.slug} ${(match? 'active' : '')}`}>
|
||||
@ -235,7 +242,7 @@ class EditForm extends Component {
|
||||
value={this.state[props.slug]} key={props.slug} />
|
||||
case 'like':
|
||||
return <LikeButton {...props} handleLike={this.handleLike}
|
||||
building_like={building_like}
|
||||
building_like={buildingLike}
|
||||
value={this.state[props.slug]} key={props.slug} />
|
||||
default:
|
||||
return null
|
||||
@ -260,6 +267,20 @@ class EditForm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
EditForm.propTypes = {
|
||||
title: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
cat: PropTypes.string,
|
||||
help: PropTypes.string,
|
||||
error: PropTypes.object,
|
||||
like: PropTypes.bool,
|
||||
building_like: PropTypes.bool,
|
||||
selectBuilding: PropTypes.func,
|
||||
building_id: PropTypes.string,
|
||||
inactive: PropTypes.bool,
|
||||
fields: PropTypes.array
|
||||
}
|
||||
|
||||
const TextInput = (props) => (
|
||||
<Fragment>
|
||||
<Label slug={props.slug} title={props.title} tooltip={props.tooltip} />
|
||||
@ -274,6 +295,17 @@ const TextInput = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
TextInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
max_length: PropTypes.number,
|
||||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
const LongTextInput = (props) => (
|
||||
<Fragment>
|
||||
<Label slug={props.slug} title={props.title} tooltip={props.tooltip} />
|
||||
@ -286,6 +318,15 @@ const LongTextInput = (props) => (
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
LongTextInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
class MultiTextInput extends Component {
|
||||
constructor(props) {
|
||||
@ -301,11 +342,11 @@ class MultiTextInput extends Component {
|
||||
}
|
||||
|
||||
edit(event) {
|
||||
const edit_i = +event.target.dataset.index;
|
||||
const edit_item = event.target.value;
|
||||
const old_values = this.getValues();
|
||||
const values = old_values.map((item, i) => {
|
||||
return i === edit_i ? edit_item : item;
|
||||
const editIndex = +event.target.dataset.index;
|
||||
const editItem = event.target.value;
|
||||
const oldValues = this.getValues();
|
||||
const values = oldValues.map((item, i) => {
|
||||
return i === editIndex ? editItem : item;
|
||||
});
|
||||
this.props.handleChange(this.props.slug, values);
|
||||
}
|
||||
@ -317,9 +358,9 @@ class MultiTextInput extends Component {
|
||||
}
|
||||
|
||||
remove(event){
|
||||
const remove_i = +event.target.dataset.index;
|
||||
const removeIndex = +event.target.dataset.index;
|
||||
const values = this.getValues().filter((_, i) => {
|
||||
return i !== remove_i;
|
||||
return i !== removeIndex;
|
||||
});
|
||||
this.props.handleChange(this.props.slug, values);
|
||||
}
|
||||
@ -355,6 +396,16 @@ class MultiTextInput extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
MultiTextInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
const TextListInput = (props) => (
|
||||
<Fragment>
|
||||
<Label slug={props.slug} title={props.title} tooltip={props.tooltip} />
|
||||
@ -374,6 +425,16 @@ const TextListInput = (props) => (
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
TextListInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.string),
|
||||
value: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
const NumberInput = (props) => (
|
||||
<Fragment>
|
||||
<Label slug={props.slug} title={props.title} tooltip={props.tooltip} />
|
||||
@ -386,6 +447,16 @@ const NumberInput = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
NumberInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
step: PropTypes.number,
|
||||
value: PropTypes.number,
|
||||
disabled: PropTypes.bool,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
class YearEstimator extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -408,6 +479,18 @@ class YearEstimator extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
YearEstimator.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
date_year: PropTypes.number,
|
||||
date_upper: PropTypes.number,
|
||||
date_lower: PropTypes.number,
|
||||
value: PropTypes.number,
|
||||
disabled: PropTypes.bool,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
const CheckboxInput = (props) => (
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="checkbox"
|
||||
@ -423,6 +506,15 @@ const CheckboxInput = (props) => (
|
||||
</div>
|
||||
)
|
||||
|
||||
CheckboxInput.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
handleChange: PropTypes.func
|
||||
}
|
||||
|
||||
const LikeButton = (props) => (
|
||||
<Fragment>
|
||||
<p className="likes">{(props.value)? props.value : 0} likes</p>
|
||||
@ -441,11 +533,27 @@ const LikeButton = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
LikeButton.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.number,
|
||||
building_like: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
handleLike: PropTypes.func
|
||||
}
|
||||
|
||||
const Label = (props) => (
|
||||
<label htmlFor={props.slug}>
|
||||
{props.title}
|
||||
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null }
|
||||
</label>
|
||||
)
|
||||
);
|
||||
|
||||
Label.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string
|
||||
}
|
||||
|
||||
export default BuildingEdit;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import urlapi from 'url';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import Tooltip from './tooltip';
|
||||
@ -26,40 +27,40 @@ const BuildingView = (props) => {
|
||||
return (
|
||||
<Sidebar title={'Data available for this building'} back={`/view/${cat}.html`}>
|
||||
{
|
||||
CONFIG.map(section_props => (
|
||||
CONFIG.map(section => (
|
||||
<DataSection
|
||||
key={section_props.slug} cat={cat}
|
||||
key={section.slug} cat={cat}
|
||||
building_id={props.building_id}
|
||||
{...section_props}>
|
||||
{...section}>
|
||||
{
|
||||
section_props.fields.map(field_props => {
|
||||
section.fields.map(field => {
|
||||
|
||||
switch (field_props.type) {
|
||||
switch (field.type) {
|
||||
case 'uprn_list':
|
||||
return <UPRNsDataEntry
|
||||
key={field_props.slug}
|
||||
title={field_props.title}
|
||||
key={field.slug}
|
||||
title={field.title}
|
||||
value={props.uprns}
|
||||
tooltip={field_props.tooltip} />
|
||||
tooltip={field.tooltip} />
|
||||
case 'text_multi':
|
||||
return <MultiDataEntry
|
||||
key={field_props.slug}
|
||||
title={field_props.title}
|
||||
value={props[field_props.slug]}
|
||||
tooltip={field_props.tooltip} />
|
||||
key={field.slug}
|
||||
title={field.title}
|
||||
value={props[field.slug]}
|
||||
tooltip={field.tooltip} />
|
||||
case 'like':
|
||||
return <LikeDataEntry
|
||||
key={field_props.slug}
|
||||
title={field_props.title}
|
||||
value={props[field_props.slug]}
|
||||
key={field.slug}
|
||||
title={field.title}
|
||||
value={props[field.slug]}
|
||||
user_building_like={props.building_like}
|
||||
tooltip={field_props.tooltip} />
|
||||
tooltip={field.tooltip} />
|
||||
default:
|
||||
return <DataEntry
|
||||
key={field_props.slug}
|
||||
title={field_props.title}
|
||||
value={props[field_props.slug]}
|
||||
tooltip={field_props.tooltip} />
|
||||
key={field.slug}
|
||||
title={field.title}
|
||||
value={props[field.slug]}
|
||||
tooltip={field.tooltip} />
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -70,6 +71,15 @@ const BuildingView = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
BuildingView.propTypes = {
|
||||
building_id: PropTypes.string,
|
||||
match: PropTypes.object,
|
||||
uprns: PropTypes.arrayOf(PropTypes.shape({
|
||||
uprn: PropTypes.string.isRequired,
|
||||
parent_uprn: PropTypes.string
|
||||
})),
|
||||
building_like: PropTypes.bool
|
||||
}
|
||||
|
||||
const DataSection = (props) => {
|
||||
const match = props.cat === props.slug;
|
||||
@ -113,6 +123,17 @@ const DataSection = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
DataSection.propTypes = {
|
||||
title: PropTypes.string,
|
||||
cat: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
intro: PropTypes.string,
|
||||
help: PropTypes.string,
|
||||
inactive: PropTypes.bool,
|
||||
building_id: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
const DataEntry = (props) => (
|
||||
<Fragment>
|
||||
<dt>
|
||||
@ -128,6 +149,12 @@ const DataEntry = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
DataEntry.propTypes = {
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.any
|
||||
}
|
||||
|
||||
const LikeDataEntry = (props) => (
|
||||
<Fragment>
|
||||
<dt>
|
||||
@ -149,13 +176,20 @@ const LikeDataEntry = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
LikeDataEntry.propTypes = {
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
user_building_like: PropTypes.bool
|
||||
}
|
||||
|
||||
const MultiDataEntry = (props) => {
|
||||
let content;
|
||||
|
||||
if (props.value && props.value.length) {
|
||||
content = <ul>{
|
||||
props.value.map((item, index) => {
|
||||
return <li key={index}><a href={sanitise_url(item)}>{item}</a></li>
|
||||
return <li key={index}><a href={sanitiseURL(item)}>{item}</a></li>
|
||||
})
|
||||
}</ul>
|
||||
} else {
|
||||
@ -173,7 +207,13 @@ const MultiDataEntry = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
function sanitise_url(string){
|
||||
MultiDataEntry.propTypes = {
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string)
|
||||
}
|
||||
|
||||
function sanitiseURL(string){
|
||||
let url_
|
||||
|
||||
// http or https
|
||||
@ -211,8 +251,8 @@ function sanitise_url(string){
|
||||
|
||||
const UPRNsDataEntry = (props) => {
|
||||
const uprns = props.value || [];
|
||||
const no_parent = uprns.filter(uprn => uprn.parent_uprn == null);
|
||||
const with_parent = uprns.filter(uprn => uprn.parent_uprn != null);
|
||||
const noParent = uprns.filter(uprn => uprn.parent_uprn == null);
|
||||
const withParent = uprns.filter(uprn => uprn.parent_uprn != null);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@ -222,18 +262,18 @@ const UPRNsDataEntry = (props) => {
|
||||
</dt>
|
||||
<dd><ul className="uprn-list">
|
||||
<Fragment>{
|
||||
no_parent.length?
|
||||
no_parent.map(uprn => (
|
||||
noParent.length?
|
||||
noParent.map(uprn => (
|
||||
<li key={uprn.uprn}>{uprn.uprn}</li>
|
||||
))
|
||||
: '\u00A0'
|
||||
}</Fragment>
|
||||
{
|
||||
with_parent.length?
|
||||
withParent.length?
|
||||
<details>
|
||||
<summary>Children</summary>
|
||||
{
|
||||
with_parent.map(uprn => (
|
||||
withParent.map(uprn => (
|
||||
<li key={uprn.uprn}>{uprn.uprn} (child of {uprn.parent_uprn})</li>
|
||||
))
|
||||
}
|
||||
@ -245,4 +285,13 @@ const UPRNsDataEntry = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
UPRNsDataEntry.propTypes = {
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.shape({
|
||||
uprn: PropTypes.string.isRequired,
|
||||
parent_uprn: PropTypes.string
|
||||
}))
|
||||
}
|
||||
|
||||
export default BuildingView;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function ErrorBox(props){
|
||||
if (props.msg) {
|
||||
@ -22,4 +23,8 @@ function ErrorBox(props){
|
||||
);
|
||||
}
|
||||
|
||||
ErrorBox.propTypes = {
|
||||
msg: PropTypes.string
|
||||
}
|
||||
|
||||
export default ErrorBox;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Logo from './logo';
|
||||
import './header.css';
|
||||
@ -100,4 +101,10 @@ class Header extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const InfoBox = (props) => (
|
||||
<Fragment>
|
||||
@ -20,4 +21,9 @@ const InfoBox = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
InfoBox.propTypes = {
|
||||
msg: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default InfoBox;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './legend.css';
|
||||
|
||||
@ -136,19 +137,23 @@ const Legend = (props) => {
|
||||
}
|
||||
{
|
||||
elements.length?
|
||||
(<ul className="data-legend">
|
||||
<ul className="data-legend">
|
||||
{
|
||||
elements.map((data_item) => (
|
||||
<LegendItem {...data_item} key={data_item.color} />
|
||||
elements.map((item) => (
|
||||
<LegendItem {...item} key={item.color} />
|
||||
))
|
||||
}
|
||||
</ul>)
|
||||
: (<p className="data-intro">Coming soon…</p>)
|
||||
</ul>
|
||||
: <p className="data-intro">Coming soon…</p>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Legend.propTypes = {
|
||||
slug: PropTypes.string
|
||||
}
|
||||
|
||||
const LegendItem = (props) => (
|
||||
<li>
|
||||
<span className="key" style={ { background: props.color } }>-</span>
|
||||
@ -156,4 +161,9 @@ const LegendItem = (props) => (
|
||||
</li>
|
||||
);
|
||||
|
||||
LegendItem.propTypes = {
|
||||
color: PropTypes.string,
|
||||
text: PropTypes.string
|
||||
}
|
||||
|
||||
export default Legend;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from './error-box';
|
||||
import InfoBox from './info-box';
|
||||
@ -126,4 +127,9 @@ class Login extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Login.propTypes = {
|
||||
login: PropTypes.func,
|
||||
user: PropTypes.object
|
||||
}
|
||||
|
||||
export default Login;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Map, TileLayer, ZoomControl, AttributionControl } from 'react-leaflet-universal';
|
||||
|
||||
import '../../node_modules/leaflet/dist/leaflet.css'
|
||||
@ -39,12 +40,12 @@ class ColouringMap extends Component {
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
const is_edit = this.props.match.url.match('edit')
|
||||
const mode = is_edit? 'edit': 'view';
|
||||
const isEdit = this.props.match.url.match('edit')
|
||||
const mode = isEdit? 'edit': 'view';
|
||||
const lat = e.latlng.lat
|
||||
const lng = e.latlng.lng
|
||||
const new_cat = parseCategoryURL(this.props.match.url);
|
||||
const map_cat = new_cat || 'age';
|
||||
const newCat = parseCategoryURL(this.props.match.url);
|
||||
const mapCat = newCat || 'age';
|
||||
fetch(
|
||||
'/buildings/locate?lat='+lat+'&lng='+lng
|
||||
).then(
|
||||
@ -53,11 +54,11 @@ class ColouringMap extends Component {
|
||||
if (data && data.length){
|
||||
const building = data[0];
|
||||
this.props.selectBuilding(building);
|
||||
this.props.history.push(`/${mode}/${map_cat}/building/${building.building_id}.html`);
|
||||
this.props.history.push(`/${mode}/${mapCat}/building/${building.building_id}.html`);
|
||||
} else {
|
||||
// deselect but keep/return to expected colour theme
|
||||
this.props.selectBuilding(undefined);
|
||||
this.props.history.push(`/${mode}/${map_cat}.html`);
|
||||
this.props.history.push(`/${mode}/${mapCat}.html`);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
(err) => console.error(err)
|
||||
@ -81,37 +82,37 @@ class ColouringMap extends Component {
|
||||
const attribution = 'Building attribute data is © Colouring London contributors. Maps contain OS data © Crown copyright: OS Maps baselayers and building outlines.'
|
||||
|
||||
// colour-data tiles
|
||||
const is_building = /building/.test(this.props.match.url);
|
||||
const is_edit = /edit/.test(this.props.match.url);
|
||||
const isBuilding = /building/.test(this.props.match.url);
|
||||
const isEdit = /edit/.test(this.props.match.url);
|
||||
const cat = parseCategoryURL(this.props.match.url);
|
||||
const tileset_by_cat = {
|
||||
const tilesetByCat = {
|
||||
age: 'date_year',
|
||||
size: 'size_storeys',
|
||||
location: 'location',
|
||||
like: 'likes',
|
||||
planning: 'conservation_area',
|
||||
}
|
||||
const data_tileset = tileset_by_cat[cat];
|
||||
const tileset = tilesetByCat[cat];
|
||||
// pick revision id to bust browser cache
|
||||
const rev = this.props.building? this.props.building.revision_id : '';
|
||||
const dataLayer = data_tileset?
|
||||
const dataLayer = tileset?
|
||||
<TileLayer
|
||||
key={data_tileset}
|
||||
url={`/tiles/${data_tileset}/{z}/{x}/{y}.png?rev=${rev}`}
|
||||
key={tileset}
|
||||
url={`/tiles/${tileset}/{z}/{x}/{y}.png?rev=${rev}`}
|
||||
minZoom={9} />
|
||||
: null;
|
||||
|
||||
// highlight
|
||||
const geometry_id = (this.props.building) ? this.props.building.geometry_id : undefined;
|
||||
const highlight = `/tiles/highlight/{z}/{x}/{y}.png?highlight=${geometry_id}`
|
||||
const highlightLayer = (is_building && this.props.building) ?
|
||||
const geometryId = (this.props.building) ? this.props.building.geometry_id : undefined;
|
||||
const highlight = `/tiles/highlight/{z}/{x}/{y}.png?highlight=${geometryId}`
|
||||
const highlightLayer = (isBuilding && this.props.building) ?
|
||||
<TileLayer
|
||||
key={this.props.building.building_id}
|
||||
url={highlight}
|
||||
minZoom={14} />
|
||||
: null;
|
||||
|
||||
const base_layer_url = (this.state.theme === 'light')?
|
||||
const baseUrl = (this.state.theme === 'light')?
|
||||
'/tiles/base_light/{z}/{x}/{y}.png'
|
||||
: '/tiles/base_night/{z}/{x}/{y}.png'
|
||||
|
||||
@ -128,16 +129,16 @@ class ColouringMap extends Component {
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<TileLayer url={url} attribution={attribution} />
|
||||
<TileLayer url={base_layer_url} minZoom={14} />
|
||||
<TileLayer url={baseUrl} minZoom={14} />
|
||||
{ dataLayer }
|
||||
{ highlightLayer }
|
||||
<ZoomControl position="topright" />
|
||||
<AttributionControl prefix="" />
|
||||
</Map>
|
||||
{
|
||||
!is_building && this.props.match.url !== '/'? (
|
||||
!isBuilding && this.props.match.url !== '/'? (
|
||||
<div className="map-notice">
|
||||
<HelpIcon /> {is_edit? 'Click a building to edit' : 'Click a building for details'}
|
||||
<HelpIcon /> {isEdit? 'Click a building to edit' : 'Click a building for details'}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
@ -146,7 +147,7 @@ class ColouringMap extends Component {
|
||||
<Fragment>
|
||||
<Legend slug={cat} />
|
||||
<ThemeSwitcher onSubmit={this.themeSwitch} currentTheme={this.state.theme} />
|
||||
<SearchBox onLocate={this.handleLocate} is_building={is_building} />
|
||||
<SearchBox onLocate={this.handleLocate} isBuilding={isBuilding} />
|
||||
</Fragment>
|
||||
) : null
|
||||
}
|
||||
@ -155,4 +156,11 @@ class ColouringMap extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ColouringMap.propTypes = {
|
||||
building: PropTypes.object,
|
||||
selectBuilding: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
history: PropTypes.object
|
||||
}
|
||||
|
||||
export default ColouringMap;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from './error-box';
|
||||
|
||||
@ -109,4 +110,16 @@ class MyAccountPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
MyAccountPage.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
username: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
registered: PropTypes.string,
|
||||
api_key: PropTypes.string,
|
||||
error: PropTypes.object
|
||||
}),
|
||||
updateUser: PropTypes.func,
|
||||
logout: PropTypes.func
|
||||
}
|
||||
|
||||
export default MyAccountPage;
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import { EditIcon } from './icons';
|
||||
import CONFIG from './fields-config.json';
|
||||
|
||||
const Overview = (props) => {
|
||||
var data_layer = 'age'; // always default
|
||||
var dataLayer = 'age'; // always default
|
||||
if (props.match && props.match.params && props.match.params.cat) {
|
||||
data_layer = props.match.params.cat;
|
||||
dataLayer = props.match.params.cat;
|
||||
}
|
||||
|
||||
if (props.mode === 'edit' && !props.user){
|
||||
@ -16,22 +17,28 @@ const Overview = (props) => {
|
||||
}
|
||||
|
||||
let title = (props.mode === 'view')? 'View maps' : 'Add or edit data';
|
||||
let back = (props.mode === 'edit')? `/view/${data_layer}.html` : undefined;
|
||||
let back = (props.mode === 'edit')? `/view/${dataLayer}.html` : undefined;
|
||||
|
||||
return (
|
||||
<Sidebar title={title} back={back}>
|
||||
{
|
||||
CONFIG.map((data_group) => (
|
||||
<OverviewSection {...data_group}
|
||||
data_layer={data_layer} key={data_group.slug} mode={props.mode} />
|
||||
CONFIG.map((dataGroup) => (
|
||||
<OverviewSection {...dataGroup}
|
||||
dataLayer={dataLayer} key={dataGroup.slug} mode={props.mode} />
|
||||
))
|
||||
}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
Overview.propTypes = {
|
||||
match: PropTypes.object,
|
||||
mode: PropTypes.string,
|
||||
user: PropTypes.object
|
||||
}
|
||||
|
||||
const OverviewSection = (props) => {
|
||||
const match = props.data_layer === props.slug;
|
||||
const match = props.dataLayer === props.slug;
|
||||
const inactive = props.inactive;
|
||||
|
||||
return (
|
||||
@ -83,4 +90,18 @@ const OverviewSection = (props) => {
|
||||
)
|
||||
};
|
||||
|
||||
OverviewSection.propTypes = {
|
||||
title: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
intro: PropTypes.string,
|
||||
help: PropTypes.string,
|
||||
dataLayer: PropTypes.string,
|
||||
mode: PropTypes.string,
|
||||
inactive: PropTypes.bool,
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
slug: PropTypes.string
|
||||
}))
|
||||
}
|
||||
|
||||
export default Overview;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './search-box.css';
|
||||
|
||||
@ -26,7 +27,7 @@ class SearchBox extends Component {
|
||||
this.setState({
|
||||
q: e.target.value
|
||||
});
|
||||
// If the ‘clear’ icon has been clicked, clear results list as well
|
||||
// If the ‘clear’ icon has been clicked, clear results list as well
|
||||
if(e.target.value === '') {
|
||||
this.clearResults();
|
||||
}
|
||||
@ -107,7 +108,7 @@ class SearchBox extends Component {
|
||||
this.props.onLocate(lat, lng, zoom);
|
||||
}}
|
||||
href={href}
|
||||
>{`${label.substring(0,4)} ${label.substring(4, 7)}`}</a>
|
||||
>{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
@ -115,7 +116,7 @@ class SearchBox extends Component {
|
||||
</ul>
|
||||
: null;
|
||||
return (
|
||||
<div className={`search-box ${this.props.is_building? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
|
||||
<div className={`search-box ${this.props.isBuilding? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
|
||||
<form action="/search" method="GET" onSubmit={this.search}
|
||||
className="form-inline">
|
||||
<input
|
||||
@ -136,4 +137,9 @@ class SearchBox extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
SearchBox.propTypes = {
|
||||
onLocate: PropTypes.func,
|
||||
isBuilding: PropTypes.bool
|
||||
}
|
||||
|
||||
export default SearchBox;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './sidebar.css';
|
||||
import { BackIcon } from './icons';
|
||||
@ -20,4 +21,10 @@ const Sidebar = (props) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
Sidebar.propTypes = {
|
||||
back: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from './error-box';
|
||||
import InfoBox from './info-box';
|
||||
@ -156,4 +157,9 @@ class SignUp extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
SignUp.propTypes = {
|
||||
login: PropTypes.func.isRequired,
|
||||
user: PropTypes.object
|
||||
}
|
||||
|
||||
export default SignUp;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './theme-switcher.css';
|
||||
|
||||
@ -11,4 +12,9 @@ const ThemeSwitcher = (props) => (
|
||||
</form>
|
||||
);
|
||||
|
||||
ThemeSwitcher.propTypes = {
|
||||
currentTheme: PropTypes.string,
|
||||
onSubmit: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './tooltip.css';
|
||||
import { InfoIcon } from './icons';
|
||||
@ -42,4 +43,9 @@ class Tooltip extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
text: PropTypes.string
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
|
@ -39,12 +39,12 @@ function parseBuildingURL(url) {
|
||||
* @returns {String} [age]
|
||||
*/
|
||||
function parseCategoryURL(url) {
|
||||
const default_cat = 'age';
|
||||
const defaultCat = 'age';
|
||||
if (url === '/') {
|
||||
return default_cat
|
||||
return defaultCat;
|
||||
}
|
||||
const matches = /^\/(view|edit)\/([^/.]+)/.exec(url);
|
||||
const cat = (matches && matches.length >= 3) ? matches[2] : default_cat;
|
||||
const cat = (matches && matches.length >= 3) ? matches[2] : defaultCat;
|
||||
return cat;
|
||||
}
|
||||
|
||||
|
@ -76,29 +76,29 @@ function frontendRoute(req, res) {
|
||||
const data = {};
|
||||
context.status = 200;
|
||||
|
||||
const user_id = req.session.user_id;
|
||||
const building_id = parseBuildingURL(req.url);
|
||||
const is_building = (typeof (building_id) !== 'undefined');
|
||||
if (is_building && isNaN(building_id)) {
|
||||
const userId = req.session.user_id;
|
||||
const buildingId = parseBuildingURL(req.url);
|
||||
const isBuilding = (typeof (buildingId) !== 'undefined');
|
||||
if (isBuilding && isNaN(buildingId)) {
|
||||
context.status = 404;
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
user_id ? getUserById(user_id) : undefined,
|
||||
is_building ? getBuildingById(building_id) : undefined,
|
||||
is_building ? getBuildingUPRNsById(building_id) : undefined,
|
||||
(is_building && user_id) ? getBuildingLikeById(building_id, user_id) : false
|
||||
userId ? getUserById(userId) : undefined,
|
||||
isBuilding ? getBuildingById(buildingId) : undefined,
|
||||
isBuilding ? getBuildingUPRNsById(buildingId) : undefined,
|
||||
(isBuilding && userId) ? getBuildingLikeById(buildingId, userId) : false
|
||||
]).then(function (values) {
|
||||
const user = values[0];
|
||||
const building = values[1];
|
||||
const uprns = values[2];
|
||||
const building_like = values[3];
|
||||
if (is_building && typeof (building) === 'undefined') {
|
||||
const buildingLike = values[3];
|
||||
if (isBuilding && typeof (building) === 'undefined') {
|
||||
context.status = 404
|
||||
}
|
||||
data.user = user;
|
||||
data.building = building;
|
||||
data.building_like = building_like;
|
||||
data.building_like = buildingLike;
|
||||
if (data.building != null) {
|
||||
data.building.uprns = uprns;
|
||||
}
|
||||
@ -218,10 +218,10 @@ server.route('/building/:building_id.json')
|
||||
}
|
||||
})
|
||||
|
||||
function updateBuilding(req, res, user_id) {
|
||||
function updateBuilding(req, res, userId) {
|
||||
const { building_id } = req.params;
|
||||
const building = req.body;
|
||||
saveBuilding(building_id, building, user_id).then(building => {
|
||||
saveBuilding(building_id, building, userId).then(building => {
|
||||
if (building.error) {
|
||||
res.send(building)
|
||||
return
|
||||
@ -384,8 +384,8 @@ server.post('/api/key', function (req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
getNewUserAPIKey(req.session.user_id).then(function (api_key) {
|
||||
res.send(api_key);
|
||||
getNewUserAPIKey(req.session.user_id).then(function (apiKey) {
|
||||
res.send(apiKey);
|
||||
}).catch(function (error) {
|
||||
res.send(error);
|
||||
});
|
||||
@ -393,14 +393,14 @@ server.post('/api/key', function (req, res) {
|
||||
|
||||
// GET search
|
||||
server.get('/search', function (req, res) {
|
||||
const search_term = req.query.q;
|
||||
if (!search_term) {
|
||||
const searchTerm = req.query.q;
|
||||
if (!searchTerm) {
|
||||
res.send({
|
||||
error: 'Please provide a search term'
|
||||
})
|
||||
return
|
||||
}
|
||||
queryLocation(search_term).then((results) => {
|
||||
queryLocation(searchTerm).then((results) => {
|
||||
if (typeof (results) === 'undefined') {
|
||||
res.send({
|
||||
error: 'Database error'
|
||||
|
@ -18,7 +18,7 @@
|
||||
// and then use stdlib `import fs from 'fs';`
|
||||
import fs from 'node-fs';
|
||||
|
||||
import { get_xyz } from './tile';
|
||||
import { getXYZ } from './tile';
|
||||
|
||||
// Use an environment variable to configure the cache location, somewhere we can read/write to.
|
||||
const CACHE_PATH = process.env.TILECACHE_PATH
|
||||
@ -32,10 +32,10 @@ const CACHE_PATH = process.env.TILECACHE_PATH
|
||||
* @param {number} y
|
||||
*/
|
||||
function get(tileset, z, x, y) {
|
||||
if (!should_try_cache(tileset, z)) {
|
||||
if (!shouldTryCache(tileset, z)) {
|
||||
return Promise.reject(`Skip cache get ${tileset}/${z}/${x}/${y}`);
|
||||
}
|
||||
const location = cache_location(tileset, z, x, y);
|
||||
const location = cacheLocation(tileset, z, x, y);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(location.fname, (err, data) => {
|
||||
if (err) {
|
||||
@ -57,10 +57,10 @@ function get(tileset, z, x, y) {
|
||||
* @param {number} y
|
||||
*/
|
||||
function put(im, tileset, z, x, y) {
|
||||
if (!should_try_cache(tileset, z)) {
|
||||
if (!shouldTryCache(tileset, z)) {
|
||||
return Promise.reject(`Skip cache put ${tileset}/${z}/${x}/${y}`);
|
||||
}
|
||||
const location = cache_location(tileset, z, x, y);
|
||||
const location = cacheLocation(tileset, z, x, y);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(location.fname, im, 'binary', (err) => {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
@ -91,7 +91,7 @@ function put(im, tileset, z, x, y) {
|
||||
* @param {number} y
|
||||
*/
|
||||
function remove(tileset, z, x, y) {
|
||||
const location = cache_location(tileset, z, x, y)
|
||||
const location = cacheLocation(tileset, z, x, y)
|
||||
return new Promise(resolve => {
|
||||
fs.unlink(location.fname, (err) => {
|
||||
if(err){
|
||||
@ -111,26 +111,26 @@ function remove(tileset, z, x, y) {
|
||||
* @param {String} tileset
|
||||
* @param {Array} bbox [w, s, e, n] in EPSG:3857 coordinates
|
||||
*/
|
||||
function remove_all_at_bbox(bbox) {
|
||||
function removeAllAtBbox(bbox) {
|
||||
// magic numbers for min/max zoom
|
||||
const min_zoom = 9;
|
||||
const max_zoom = 18;
|
||||
const minZoom = 9;
|
||||
const maxZoom = 18;
|
||||
// magic list of tilesets - see tileserver, other cache rules
|
||||
const tilesets = ['date_year', 'size_storeys', 'location', 'likes', 'conservation_area'];
|
||||
let tile_bounds;
|
||||
const remove_promises = [];
|
||||
let tileBounds;
|
||||
const removePromises = [];
|
||||
for (let ti = 0; ti < tilesets.length; ti++) {
|
||||
const tileset = tilesets[ti];
|
||||
for (let z = min_zoom; z <= max_zoom; z++) {
|
||||
tile_bounds = get_xyz(bbox, z)
|
||||
for (let x = tile_bounds.minX; x <= tile_bounds.maxX; x++){
|
||||
for (let y = tile_bounds.minY; y <= tile_bounds.maxY; y++){
|
||||
remove_promises.push(remove(tileset, z, x, y))
|
||||
for (let z = minZoom; z <= maxZoom; z++) {
|
||||
tileBounds = getXYZ(bbox, z)
|
||||
for (let x = tileBounds.minX; x <= tileBounds.maxX; x++){
|
||||
for (let y = tileBounds.minY; y <= tileBounds.maxY; y++){
|
||||
removePromises.push(remove(tileset, z, x, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Promise.all(remove_promises)
|
||||
Promise.all(removePromises)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,7 +142,7 @@ function remove_all_at_bbox(bbox) {
|
||||
* @param {number} y
|
||||
* @returns {object} { dir: <directory>, fname: <full filepath> }
|
||||
*/
|
||||
function cache_location(tileset, z, x, y) {
|
||||
function cacheLocation(tileset, z, x, y) {
|
||||
const dir = `${CACHE_PATH}/${tileset}/${z}/${x}`
|
||||
const fname = `${dir}/${y}.png`
|
||||
return {dir, fname}
|
||||
@ -155,7 +155,7 @@ function cache_location(tileset, z, x, y) {
|
||||
* @param {number} z zoom level
|
||||
* @returns {boolean} whether to use the cache (or not)
|
||||
*/
|
||||
function should_try_cache(tileset, z) {
|
||||
function shouldTryCache(tileset, z) {
|
||||
if (tileset === 'date_year') {
|
||||
// cache high zoom because of front page hits
|
||||
return z <= 16
|
||||
@ -168,4 +168,4 @@ function should_try_cache(tileset, z) {
|
||||
return z <= 13
|
||||
}
|
||||
|
||||
export { get, put, remove, remove_all_at_bbox };
|
||||
export { get, put, remove, removeAllAtBbox };
|
||||
|
@ -33,11 +33,11 @@ const PROJ4_STRING = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x
|
||||
|
||||
// Mapnik uses table definitions to query geometries and attributes from PostGIS.
|
||||
// The queries here are eventually used as subqueries when Mapnik fetches data to render a
|
||||
// tile - so given a table_definition like:
|
||||
// (SELECT geometry_geom FROM geometries) as my_table_definition
|
||||
// tile - so given a table definition like:
|
||||
// (SELECT geometry_geom FROM geometries) as def
|
||||
// Mapnik will wrap it in a bbox query and PostGIS will eventually see something like:
|
||||
// SELECT AsBinary("geometry") AS geom from
|
||||
// (SELECT geometry_geom FROM geometries) as my_table_definition
|
||||
// (SELECT geometry_geom FROM geometries) as def
|
||||
// WHERE "geometry" && SetSRID('BOX3D(0,1,2,3)'::box3d, 3857)
|
||||
// see docs: https://github.com/mapnik/mapnik/wiki/OptimizeRenderingWithPostGIS
|
||||
const MAP_STYLE_TABLE_DEFINITIONS = {
|
||||
@ -139,26 +139,26 @@ const mercator = new SphericalMercator({
|
||||
size: TILE_SIZE
|
||||
});
|
||||
|
||||
function get_bbox(z, x, y) {
|
||||
function getBbox(z, x, y) {
|
||||
return mercator.bbox(x, y, z, false, '900913');
|
||||
}
|
||||
|
||||
function get_xyz(bbox, z) {
|
||||
function getXYZ(bbox, z) {
|
||||
return mercator.xyz(bbox, z, false, '900913')
|
||||
}
|
||||
|
||||
function render_tile(tileset, z, x, y, geometry_id, cb) {
|
||||
const bbox = get_bbox(z, x, y)
|
||||
function renderTile(tileset, z, x, y, geometryId, cb) {
|
||||
const bbox = getBbox(z, x, y)
|
||||
|
||||
const map = new mapnik.Map(TILE_SIZE, TILE_SIZE, PROJ4_STRING);
|
||||
map.bufferSize = TILE_BUFFER_SIZE;
|
||||
const layer = new mapnik.Layer('tile', PROJ4_STRING);
|
||||
|
||||
const table_def = (tileset === 'highlight') ?
|
||||
get_highlight_table_def(geometry_id)
|
||||
const tableDefinition = (tileset === 'highlight') ?
|
||||
getHighlightTableDefinition(geometryId)
|
||||
: MAP_STYLE_TABLE_DEFINITIONS[tileset];
|
||||
|
||||
const conf = Object.assign({ table: table_def }, DATASOURCE_CONFIG)
|
||||
const conf = Object.assign({ table: tableDefinition }, DATASOURCE_CONFIG)
|
||||
|
||||
var postgis;
|
||||
try {
|
||||
@ -186,17 +186,17 @@ function render_tile(tileset, z, x, y, geometry_id, cb) {
|
||||
}
|
||||
}
|
||||
|
||||
// highlight single geometry, requires geometry_id in the table query
|
||||
function get_highlight_table_def(geometry_id) {
|
||||
// highlight single geometry, requires geometryId in the table query
|
||||
function getHighlightTableDefinition(geometryId) {
|
||||
return `(
|
||||
SELECT
|
||||
g.geometry_geom
|
||||
FROM
|
||||
geometries as g
|
||||
WHERE
|
||||
g.geometry_id = ${geometry_id}
|
||||
g.geometry_id = ${geometryId}
|
||||
) as highlight`
|
||||
}
|
||||
|
||||
|
||||
export { get_bbox, get_xyz, render_tile, TILE_SIZE };
|
||||
export { getBbox, getXYZ, renderTile, TILE_SIZE };
|
||||
|
@ -9,7 +9,7 @@ import express from 'express';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { get, put } from './cache';
|
||||
import { render_tile, get_bbox, get_xyz, TILE_SIZE } from './tile';
|
||||
import { renderTile, getBbox, getXYZ, TILE_SIZE } from './tile';
|
||||
import { strictParseInt } from '../parse';
|
||||
|
||||
// zoom level when we switch from rendering direct from database to instead composing tiles
|
||||
@ -23,48 +23,48 @@ const EXTENT_BBOX = [-61149.622628, 6667754.851372, 28128.826409, 6744803.375884
|
||||
// tiles router
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/highlight/:z/:x/:y.png', handle_highlight_tile_request);
|
||||
router.get('/highlight/:z/:x/:y.png', handleHighlightTileRequest);
|
||||
|
||||
router.get('/base_light/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('base_light', req, res)
|
||||
handleTileRequest('base_light', req, res)
|
||||
});
|
||||
|
||||
router.get('/base_night/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('base_night', req, res)
|
||||
handleTileRequest('base_night', req, res)
|
||||
});
|
||||
|
||||
router.get('/date_year/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('date_year', req, res)
|
||||
handleTileRequest('date_year', req, res)
|
||||
});
|
||||
|
||||
router.get('/size_storeys/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('size_storeys', req, res)
|
||||
handleTileRequest('size_storeys', req, res)
|
||||
});
|
||||
|
||||
router.get('/location/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('location', req, res)
|
||||
handleTileRequest('location', req, res)
|
||||
});
|
||||
|
||||
router.get('/likes/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('likes', req, res)
|
||||
handleTileRequest('likes', req, res)
|
||||
});
|
||||
|
||||
router.get('/conservation_area/:z/:x/:y.png', (req, res) => {
|
||||
handle_tile_request('conservation_area', req, res)
|
||||
handleTileRequest('conservation_area', req, res)
|
||||
});
|
||||
|
||||
function handle_tile_request(tileset, req, res) {
|
||||
function handleTileRequest(tileset, req, res) {
|
||||
const { z, x, y } = req.params
|
||||
const int_z = strictParseInt(z);
|
||||
const int_x = strictParseInt(x);
|
||||
const int_y = strictParseInt(y);
|
||||
const intZ = strictParseInt(z);
|
||||
const intX = strictParseInt(x);
|
||||
const intY = strictParseInt(y);
|
||||
|
||||
if (isNaN(int_x) || isNaN(int_y) || isNaN(int_z)) {
|
||||
if (isNaN(intX) || isNaN(intY) || isNaN(intZ)) {
|
||||
console.error('Missing x or y or z')
|
||||
return { error: 'Bad parameter' }
|
||||
}
|
||||
|
||||
load_tile(tileset, int_z, int_x, int_y).then((im) => {
|
||||
loadTile(tileset, intZ, intX, intY).then((im) => {
|
||||
res.writeHead(200, { 'Content-Type': 'image/png' })
|
||||
res.end(im)
|
||||
}).catch((err) => {
|
||||
@ -73,21 +73,21 @@ function handle_tile_request(tileset, req, res) {
|
||||
})
|
||||
}
|
||||
|
||||
function load_tile(tileset, z, x, y) {
|
||||
if (outside_extent(z, x, y)) {
|
||||
return empty_tile()
|
||||
function loadTile(tileset, z, x, y) {
|
||||
if (outsideExtent(z, x, y)) {
|
||||
return emptyTile()
|
||||
}
|
||||
return get(tileset, z, x, y).then((im) => {
|
||||
console.log(`From cache ${tileset}/${z}/${x}/${y}`)
|
||||
return im
|
||||
}).catch(() => {
|
||||
return render_or_stitch_tile(tileset, z, x, y)
|
||||
return renderOrStitchTile(tileset, z, x, y)
|
||||
})
|
||||
}
|
||||
|
||||
function render_or_stitch_tile(tileset, z, x, y) {
|
||||
function renderOrStitchTile(tileset, z, x, y) {
|
||||
if (z <= STITCH_THRESHOLD) {
|
||||
return stitch_tile(tileset, z, x, y).then(im => {
|
||||
return StitchTile(tileset, z, x, y).then(im => {
|
||||
return put(im, tileset, z, x, y).then(() => {
|
||||
console.log(`Stitch ${tileset}/${z}/${x}/${y}`)
|
||||
return im
|
||||
@ -99,7 +99,7 @@ function render_or_stitch_tile(tileset, z, x, y) {
|
||||
} else {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
render_tile(tileset, z, x, y, undefined, (err, im) => {
|
||||
renderTile(tileset, z, x, y, undefined, (err, im) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
@ -116,12 +116,12 @@ function render_or_stitch_tile(tileset, z, x, y) {
|
||||
}
|
||||
}
|
||||
|
||||
function outside_extent(z, x, y) {
|
||||
const xy = get_xyz(EXTENT_BBOX, z);
|
||||
function outsideExtent(z, x, y) {
|
||||
const xy = getXYZ(EXTENT_BBOX, z);
|
||||
return xy.minY > y || xy.maxY < y || xy.minX > x || xy.maxX < x;
|
||||
}
|
||||
|
||||
function empty_tile() {
|
||||
function emptyTile() {
|
||||
return sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
@ -132,22 +132,22 @@ function empty_tile() {
|
||||
}).png().toBuffer()
|
||||
}
|
||||
|
||||
function stitch_tile(tileset, z, x, y) {
|
||||
const bbox = get_bbox(z, x, y)
|
||||
const next_z = z + 1
|
||||
const next_xy = get_xyz(bbox, next_z)
|
||||
function StitchTile(tileset, z, x, y) {
|
||||
const bbox = getBbox(z, x, y)
|
||||
const nextZ = z + 1
|
||||
const nextXY = getXYZ(bbox, nextZ)
|
||||
|
||||
return Promise.all([
|
||||
// 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),
|
||||
load_tile(tileset, next_z, next_xy.maxX, next_xy.maxY)
|
||||
loadTile(tileset, nextZ, nextXY.minX, nextXY.minY),
|
||||
loadTile(tileset, nextZ, nextXY.maxX, nextXY.minY),
|
||||
loadTile(tileset, nextZ, nextXY.minX, nextXY.maxY),
|
||||
loadTile(tileset, nextZ, nextXY.maxX, nextXY.maxY)
|
||||
]).then(([
|
||||
top_left,
|
||||
top_right,
|
||||
bottom_left,
|
||||
bottom_right
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight
|
||||
]) => {
|
||||
// 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)
|
||||
@ -160,18 +160,18 @@ function stitch_tile(tileset, z, x, y) {
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
}
|
||||
}).overlayWith(
|
||||
top_left, { gravity: sharp.gravity.northwest }
|
||||
topLeft, { gravity: sharp.gravity.northwest }
|
||||
).png().toBuffer().then((buf) => {
|
||||
return sharp(buf).overlayWith(
|
||||
top_right, { gravity: sharp.gravity.northeast }
|
||||
topRight, { gravity: sharp.gravity.northeast }
|
||||
).png().toBuffer()
|
||||
}).then((buf) => {
|
||||
return sharp(buf).overlayWith(
|
||||
bottom_left, { gravity: sharp.gravity.southwest }
|
||||
bottomLeft, { gravity: sharp.gravity.southwest }
|
||||
).png().toBuffer()
|
||||
}).then((buf) => {
|
||||
return sharp(buf).overlayWith(
|
||||
bottom_right, { gravity: sharp.gravity.southeast }
|
||||
bottomRight, { gravity: sharp.gravity.southeast }
|
||||
).png().toBuffer()
|
||||
}).then((buf) => {
|
||||
return sharp(buf
|
||||
@ -182,30 +182,30 @@ function stitch_tile(tileset, z, x, y) {
|
||||
}
|
||||
|
||||
|
||||
function handle_highlight_tile_request(req, res) {
|
||||
function handleHighlightTileRequest(req, res) {
|
||||
const { z, x, y } = req.params
|
||||
const int_z = strictParseInt(z);
|
||||
const int_x = strictParseInt(x);
|
||||
const int_y = strictParseInt(y);
|
||||
const intZ = strictParseInt(z);
|
||||
const intX = strictParseInt(x);
|
||||
const intY = strictParseInt(y);
|
||||
|
||||
if (isNaN(int_x) || isNaN(int_y) || isNaN(int_z)) {
|
||||
if (isNaN(intX) || isNaN(intY) || isNaN(intZ)) {
|
||||
console.error('Missing x or y or z')
|
||||
return { error: 'Bad parameter' }
|
||||
}
|
||||
|
||||
// highlight layer uses geometry_id to outline a single building
|
||||
const { highlight } = req.query
|
||||
const geometry_id = strictParseInt(highlight);
|
||||
if (isNaN(geometry_id)) {
|
||||
const geometryId = strictParseInt(highlight);
|
||||
if (isNaN(geometryId)) {
|
||||
res.status(400).send({ error: 'Bad parameter' })
|
||||
return
|
||||
}
|
||||
|
||||
if (outside_extent(z, x, y)) {
|
||||
return empty_tile()
|
||||
if (outsideExtent(z, x, y)) {
|
||||
return emptyTile()
|
||||
}
|
||||
|
||||
render_tile('highlight', int_z, int_x, int_y, geometry_id, function (err, im) {
|
||||
renderTile('highlight', intZ, intX, intY, geometryId, function (err, im) {
|
||||
if (err) {throw err}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'image/png' })
|
||||
|
Loading…
Reference in New Issue
Block a user