diff --git a/app/package-lock.json b/app/package-lock.json index 7d276854..a539b4d9 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -855,7 +855,6 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.2" }, @@ -863,8 +862,7 @@ "regenerator-runtime": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", - "dev": true + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" } } }, @@ -1037,6 +1035,12 @@ "@types/node": "*" } }, + "@types/geojson": { + "version": "7946.0.7", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", + "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", + "dev": true + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -1075,6 +1079,15 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/leaflet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.1.tgz", + "integrity": "sha512-E5k+vyE2Tv9wQsO6ZsEy08Pjd8RjHPkCzz3Ubt7feMc+5+VkbXtcZMcciczRWuMN5rFIsVywLxRhvTp7fAbbzg==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -1139,6 +1152,16 @@ "@types/react": "*" } }, + "@types/react-leaflet": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.4.0.tgz", + "integrity": "sha512-kDZ2Ky6FQxXRODBEFlq25Lu80Nc7UsDSHCmHTa22UQn2RIJxe3O443K0vzOrFyzWPpVEOmqBpfDkX9QSTBoFxg==", + "dev": true, + "requires": { + "@types/leaflet": "*", + "@types/react": "*" + } + }, "@types/react-router": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.0.3.tgz", @@ -7062,6 +7085,11 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", @@ -7244,25 +7272,16 @@ "dev": true }, "history": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", "requires": { - "invariant": "^2.2.1", + "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", "resolve-pathname": "^2.2.0", - "value-equal": "^0.4.0", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" } }, "hmac-drbg": { @@ -7277,9 +7296,12 @@ } }, "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } }, "home-or-tmp": { "version": "2.0.0", @@ -7901,6 +7923,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -9451,6 +9474,16 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, "mini-css-extract-plugin": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.5.tgz", @@ -10515,7 +10548,7 @@ }, "semver": { "version": "4.3.2", - "resolved": "http://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" } } @@ -13840,17 +13873,20 @@ "integrity": "sha512-Wl0p9HonxGnyTly+gfiotj5f0Su/ZD7omZ5ko0ji3lEuJe4nsokbz9UcLt9CaR/C33Ha4OmzPn8I/XqwKJUs7g==" }, "react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", + "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", "requires": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" }, "dependencies": { "isarray": { @@ -13869,16 +13905,17 @@ } }, "react-router-dom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", - "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", + "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", "requires": { - "history": "^4.7.2", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" + "prop-types": "^15.6.2", + "react-router": "5.0.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" } }, "read-pkg": { @@ -16114,6 +16151,16 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-invariant": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -17002,14 +17049,6 @@ "makeerror": "1.0.x" } }, - "warning": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", - "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", - "requires": { - "loose-envify": "^1.0.0" - } - }, "watch": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", diff --git a/app/package.json b/app/package.json index f9e2c505..2bd6d004 100644 --- a/app/package.json +++ b/app/package.json @@ -32,7 +32,7 @@ "react-dom": "^16.9.0", "react-leaflet": "^1.0.1", "react-leaflet-universal": "^1.2.0", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.0.1", "serialize-javascript": "^1.7.0", "sharp": "^0.21.3" }, @@ -45,6 +45,7 @@ "@types/prop-types": "^15.7.1", "@types/react": "^16.9.1", "@types/react-dom": "^16.8.5", + "@types/react-leaflet": "^2.4.0", "@types/react-router-dom": "^4.3.4", "@types/webpack-env": "^1.14.0", "babel-eslint": "^10.0.2", diff --git a/app/src/client.tsx b/app/src/client.tsx index b2653df5..e0671324 100644 --- a/app/src/client.tsx +++ b/app/src/client.tsx @@ -2,7 +2,7 @@ * Client-side entry point to shared frontend React App * */ -import BrowserRouter from 'react-router-dom/BrowserRouter'; +import { BrowserRouter } from 'react-router-dom'; import React from 'react'; import { hydrate } from 'react-dom'; diff --git a/app/src/frontend/app.tsx b/app/src/frontend/app.tsx index c2df0a9f..04f8236c 100644 --- a/app/src/frontend/app.tsx +++ b/app/src/frontend/app.tsx @@ -1,31 +1,30 @@ import React, { Fragment } from 'react'; import { Route, Switch, Link } from 'react-router-dom'; import PropTypes from 'prop-types'; -import { parse } from 'query-string'; import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; import './app.css'; -import BuildingView from './building/building-view'; -import ColouringMap from './map/map'; import Header from './header'; -import MultiEdit from './building/multi-edit'; -import Categories from './building/categories'; -import Sidebar from './building/sidebar'; import AboutPage from './pages/about'; import ContributorAgreementPage from './pages/contributor-agreement'; import PrivacyPolicyPage from './pages/privacy-policy'; -import Welcome from './pages/welcome'; import Login from './user/login'; import MyAccountPage from './user/my-account'; import SignUp from './user/signup'; - import ForgottenPassword from './user/forgotten-password'; import PasswordReset from './user/password-reset'; -import { parseCategoryURL } from '../parse'; +import MapApp from './map-app'; + + +interface AppProps { + user?: any; + building?: any; + building_like?: boolean; +} /** * App component @@ -39,35 +38,28 @@ import { parseCategoryURL } from '../parse'; * map or other pages are rendered, based on the URL. Use a react-router-dom in * child components to navigate without a full page reload. */ -class App extends React.Component { // TODO: add proper types +class App extends React.Component { // TODO: add proper types static propTypes = { // TODO: generate propTypes from TS user: PropTypes.object, building: PropTypes.object, building_like: PropTypes.bool - } + }; - constructor(props) { + constructor(props: Readonly) { super(props); - // set building revision id, default 0 - const rev = (props.building)? +props.building.revision_id : 0; + this.state = { - user: props.user, - building: props.building, - building_like: props.building_like, - revision_id: rev + user: props.user }; this.login = this.login.bind(this); this.updateUser = this.updateUser.bind(this); this.logout = this.logout.bind(this); - this.selectBuilding = this.selectBuilding.bind(this); - this.colourBuilding = this.colourBuilding.bind(this); - this.increaseRevision = this.increaseRevision.bind(this); } login(user) { if (user.error) { this.logout(); - return + return; } this.setState({user: user}); } @@ -80,171 +72,12 @@ class App extends React.Component { // TODO: add proper types this.setState({user: undefined}); } - increaseRevision(revisionId) { - revisionId = +revisionId; - // bump revision id, only ever increasing - if (revisionId > this.state.revision_id){ - this.setState({revision_id: revisionId}) - } - } - - selectBuilding(building) { - this.increaseRevision(building.revision_id); - // get UPRNs and update - fetch(`/api/buildings/${building.building_id}/uprns.json`, { - method: 'GET', - headers:{ - 'Content-Type': 'application/json' - }, - credentials: 'same-origin' - }).then( - res => res.json() - ).then((res) => { - if (res.error) { - console.error(res); - } else { - building.uprns = res.uprns; - this.setState({building: building}); - } - }).catch((err) => { - console.error(err) - this.setState({building: building}); - }); - - // get if liked and update - fetch(`/api/buildings/${building.building_id}/like.json`, { - method: 'GET', - headers:{ - 'Content-Type': 'application/json' - }, - credentials: 'same-origin' - }).then( - res => res.json() - ).then((res) => { - if (res.error) { - console.error(res); - } else { - this.setState({building_like: res.like}); - } - }).catch((err) => { - console.error(err) - this.setState({building_like: false}); - }); - } - - /** - * Colour building - * - * Used in multi-edit mode to colour buildings on map click - * - * Pulls data from URL to form update - * - * @param {object} building - */ - colourBuilding(building) { - const cat = parseCategoryURL(window.location.pathname); - const q = parse(window.location.search); - const data = (cat === 'like')? {like: true}: JSON.parse(q.data as string); // TODO: verify what happens if data is string[] - if (cat === 'like'){ - this.likeBuilding(building.building_id) - } else { - this.updateBuilding(building.building_id, data) - } - } - - likeBuilding(buildingId) { - fetch(`/api/buildings/${buildingId}/like.json`, { - method: 'POST', - headers:{ - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({like: true}) - }).then( - res => res.json() - ).then(function(res){ - if (res.error) { - console.error({error: res.error}) - } else { - this.increaseRevision(res.revision_id); - } - }.bind(this)).catch( - (err) => console.error({error: err}) - ); - } - - updateBuilding(buildingId, data){ - fetch(`/api/buildings/${buildingId}.json`, { - method: 'POST', - body: JSON.stringify(data), - headers:{ - 'Content-Type': 'application/json' - }, - credentials: 'same-origin' - }).then( - res => res.json() - ).then(res => { - if (res.error) { - console.error({error: res.error}) - } else { - this.increaseRevision(res.revision_id); - } - }).catch( - (err) => console.error({error: err}) - ); - } - render() { - const building_id = (this.state.building)? - this.state.building.building_id - : 2503371 // Default to UCL main building. TODO use last selected if any - const building = this.state.building; - const building_like = this.state.building_like; return (
- - - - - - - - - - - - - - ( - - ) } /> - ( - - ) } /> - - - ( - - ) } /> @@ -263,6 +96,14 @@ class App extends React.Component { // TODO: add proper types + ( + + )} />
diff --git a/app/src/frontend/building/building-not-found.tsx b/app/src/frontend/building/building-not-found.tsx index a1b8510a..cd549453 100644 --- a/app/src/frontend/building/building-not-found.tsx +++ b/app/src/frontend/building/building-not-found.tsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import Sidebar from './sidebar'; import InfoBox from '../components/info-box'; @@ -11,12 +10,12 @@ interface BuildingNotFoundProps { } const BuildingNotFound: React.FunctionComponent = (props) => ( - +
- Back to categories + Back to categories
-
+ ); BuildingNotFound.propTypes = { diff --git a/app/src/frontend/building/building-view.tsx b/app/src/frontend/building/building-view.tsx index 480f5645..1aaab34a 100644 --- a/app/src/frontend/building/building-view.tsx +++ b/app/src/frontend/building/building-view.tsx @@ -21,15 +21,11 @@ import LikeContainer from './data-containers/like'; * @param props */ const BuildingView = (props) => { - if (typeof(props.building) === "undefined"){ - return - } - switch (props.cat) { case 'location': return { case 'use': return { case 'type': return { case 'age': return { case 'size': return { case 'construction': return { case 'team': return { case 'sustainability': return { case 'greenery': return { case 'community': return { case 'planning': return { case 'like': return ( @@ -124,11 +122,15 @@ Categories.propTypes = { building_id: PropTypes.number } -const Category = (props) => ( +const Category = (props) => { + let categoryLink = `/${props.mode}/${props.slug}`; + if (props.building_id != undefined) categoryLink += `/${props.building_id}`; + + return (
  • (

    {props.desc}

    - More info + More
  • -) + ); +} Category.propTypes = { title: PropTypes.string, diff --git a/app/src/frontend/building/container-header.tsx b/app/src/frontend/building/container-header.tsx index 184bc47e..2696ceea 100644 --- a/app/src/frontend/building/container-header.tsx +++ b/app/src/frontend/building/container-header.tsx @@ -6,17 +6,17 @@ import { BackIcon, EditIcon, ViewIcon }from '../components/icons'; const ContainerHeader: React.FunctionComponent = (props) => (
    - +

    {props.title}