import { parse } from 'query-string'; import React, { Fragment } from 'react'; import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { strictParseInt } from '../parse'; import BuildingView from './building/building-view'; import Categories from './building/categories'; import { EditHistory } from './building/edit-history/edit-history'; import MultiEdit from './building/multi-edit'; import Sidebar from './building/sidebar'; import ColouringMap from './map/map'; import { Building } from './models/building'; import Welcome from './pages/welcome'; interface MapAppRouteParams { mode: 'view' | 'edit' | 'multi-edit'; category: string; building?: string; } interface MapAppProps extends RouteComponentProps { building?: Building; building_like?: boolean; user?: any; revisionId?: number; } interface MapAppState { category: string; revision_id: number; building: Building; building_like: boolean; } class MapApp extends React.Component { constructor(props: Readonly) { super(props); this.state = { category: this.getCategory(props.match.params.category), revision_id: props.revisionId || 0, building: props.building, building_like: props.building_like }; this.selectBuilding = this.selectBuilding.bind(this); this.colourBuilding = this.colourBuilding.bind(this); this.increaseRevision = this.increaseRevision.bind(this); } componentWillReceiveProps(props: Readonly) { const newCategory = this.getCategory(props.match.params.category); if (newCategory != undefined) { this.setState({ category: newCategory }); } } componentDidMount() { this.fetchLatestRevision(); this.fetchBuildingData(); } async fetchLatestRevision() { try { const res = await fetch(`/api/buildings/revision`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' }); const data = await res.json(); this.increaseRevision(data.latestRevisionId); } catch(error) { console.error(error); } } /** * Fetches building data if a building is selected but no data provided through * props (from server-side rendering) */ async fetchBuildingData() { if(this.props.match.params.building != undefined && this.props.building == undefined) { try { // TODO: simplify API calls, create helpers for fetching data const buildingId = strictParseInt(this.props.match.params.building); let [building, building_uprns, building_like] = await Promise.all([ fetch(`/api/buildings/${buildingId}.json`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then(res => res.json()), fetch(`/api/buildings/${buildingId}/uprns.json`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then(res => res.json()), fetch(`/api/buildings/${buildingId}/like.json`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' }).then(res => res.json()) ]); building.uprns = building_uprns.uprns; this.setState({ building: building, building_like: building_like.like }); } catch(error) { console.error(error); // TODO: add UI for API errors } } } getCategory(category: string) { if (category === 'categories') return undefined; return category; } increaseRevision(revisionId) { revisionId = +revisionId; // bump revision id, only ever increasing if (revisionId > this.state.revision_id) { this.setState({ revision_id: revisionId }); } } selectBuilding(building: Building) { const mode = this.props.match.params.mode || 'view'; const category = this.props.match.params.category || 'age'; if (building == undefined) { this.setState({ building: undefined }); this.props.history.push(`/${mode}/${category}`); return; } 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 }); this.props.history.push(`/${mode}/${category}/${building.building_id}`); } }).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 = this.props.match.params.category; const q = parse(window.location.search); if (cat === 'like') { this.likeBuilding(building.building_id); } else { try { // TODO: verify what happens if data is string[] const data = JSON.parse(q.data as string); this.updateBuilding(building.building_id, data); } catch (error) { console.error(error, q); } } } 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 mode = this.props.match.params.mode; const viewEditMode = mode === 'multi-edit' ? undefined : mode; let category = this.state.category || 'age'; const building_id = this.state.building && this.state.building.building_id; return ( ( )} /> ()} /> ); } } export default MapApp;