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