Merge pull request #497 from mz8i/feature/sort-imports

Lint imports, semicolons
This commit is contained in:
mz8i 2019-11-13 13:14:27 +01:00 committed by GitHub
commit 49fd95d980
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 376 additions and 342 deletions

View File

@ -1,12 +1,11 @@
import express from 'express';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import express from 'express';
import { authUser, getNewUserAPIKey, logout } from './services/user';
import { queryLocation } from './services/search';
import buildingsRouter from './routes/buildingsRouter'; import buildingsRouter from './routes/buildingsRouter';
import usersRouter from './routes/usersRouter';
import extractsRouter from './routes/extractsRouter'; import extractsRouter from './routes/extractsRouter';
import usersRouter from './routes/usersRouter';
import { queryLocation } from './services/search';
import { authUser, getNewUserAPIKey, logout } from './services/user';
const server = express.Router(); const server = express.Router();
@ -29,7 +28,7 @@ server.post('/login', function (req, res) {
res.send(user); res.send(user);
}).catch(function (error) { }).catch(function (error) {
res.send(error); res.send(error);
}) });
}); });
// POST user logout // POST user logout
@ -46,7 +45,7 @@ server.post('/logout', function (req, res) {
server.post('/api/key', function (req, res) { server.post('/api/key', function (req, res) {
if (!req.session.user_id) { if (!req.session.user_id) {
res.send({ error: 'Must be logged in' }); res.send({ error: 'Must be logged in' });
return return;
} }
getNewUserAPIKey(req.session.user_id).then(function (apiKey) { getNewUserAPIKey(req.session.user_id).then(function (apiKey) {
@ -54,7 +53,7 @@ server.post('/api/key', function (req, res) {
}).catch(function (error) { }).catch(function (error) {
res.send(error); res.send(error);
}); });
}) });
// GET search // GET search
server.get('/search', function (req, res) { server.get('/search', function (req, res) {
@ -62,20 +61,20 @@ server.get('/search', function (req, res) {
if (!searchTerm) { if (!searchTerm) {
res.send({ res.send({
error: 'Please provide a search term' error: 'Please provide a search term'
}) });
return return;
} }
queryLocation(searchTerm).then((results) => { queryLocation(searchTerm).then((results) => {
if (typeof (results) === 'undefined') { if (typeof (results) === 'undefined') {
res.send({ res.send({
error: 'Database error' error: 'Database error'
}) });
return return;
} }
res.send({ res.send({
results: results.map(item => { results: results.map(item => {
// map from DB results to GeoJSON Feature objects // map from DB results to GeoJSON Feature objects
const geom = JSON.parse(item.st_asgeojson) const geom = JSON.parse(item.st_asgeojson);
return { return {
type: 'Feature', type: 'Feature',
attributes: { attributes: {
@ -83,13 +82,13 @@ server.get('/search', function (req, res) {
zoom: item.zoom || 9 zoom: item.zoom || 9
}, },
geometry: geom geometry: geom
} };
}) })
}) });
}).catch(function (error) { }).catch(function (error) {
res.send(error); res.send(error);
}); });
}) });
server.use((err, req, res, next) => { server.use((err, req, res, next) => {
if (res.headersSent) { if (res.headersSent) {
@ -104,7 +103,7 @@ server.use((err, req, res, next) => {
server.use((req, res) => { server.use((req, res) => {
res.status(404).json({ error: 'Resource not found'}); res.status(404).json({ error: 'Resource not found'});
}) });
export default server; export default server;

View File

@ -1,7 +1,8 @@
import express from 'express'; import express from 'express';
import asyncController from '../routes/asyncController';
import * as buildingService from '../services/building'; import * as buildingService from '../services/building';
import * as userService from '../services/user'; import * as userService from '../services/user';
import asyncController from '../routes/asyncController';
// GET buildings // GET buildings
@ -106,7 +107,7 @@ const getBuildingLikeById = asyncController(async (req: express.Request, res: ex
// any value returned means like // any value returned means like
res.send({ like: like }); res.send({ like: like });
} catch(error) { } catch(error) {
res.send({ error: 'Database error' }) res.send({ error: 'Database error' });
} }
}); });

View File

@ -1,6 +1,7 @@
import express from 'express'; import express from 'express';
import * as dataExtractService from '../services/dataExtract';
import asyncController from '../routes/asyncController'; import asyncController from '../routes/asyncController';
import * as dataExtractService from '../services/dataExtract';
const getAllDataExtracts = asyncController(async function(req: express.Request, res: express.Response) { const getAllDataExtracts = asyncController(async function(req: express.Request, res: express.Response) {

View File

@ -1,11 +1,9 @@
import { URL } from 'url';
import express from 'express'; import express from 'express';
import * as userService from '../services/user'; import asyncController from '../routes/asyncController';
import * as passwordResetService from '../services/passwordReset'; import * as passwordResetService from '../services/passwordReset';
import { TokenVerificationError } from '../services/passwordReset'; import { TokenVerificationError } from '../services/passwordReset';
import asyncController from '../routes/asyncController'; import * as userService from '../services/user';
import { ValidationError } from '../validation'; import { ValidationError } from '../validation';
const createUser = asyncController(async (req: express.Request, res: express.Response) => { const createUser = asyncController(async (req: express.Request, res: express.Response) => {

View File

@ -1,4 +1,4 @@
import { Request, Response, NextFunction } from 'express'; import { NextFunction, Request, Response } from 'express';
/** /**
* A wrapper for controller functions that return a Promise, enabling them to be used with Express * A wrapper for controller functions that return a Promise, enabling them to be used with Express

View File

@ -2,10 +2,11 @@
* Building data access * Building data access
* *
*/ */
import { ITask } from 'pg-promise';
import db from '../../db'; import db from '../../db';
import { tileCache } from '../../tiles/rendererDefinition'; import { tileCache } from '../../tiles/rendererDefinition';
import { BoundingBox } from '../../tiles/types'; import { BoundingBox } from '../../tiles/types';
import { ITask } from 'pg-promise';
// 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.
@ -276,7 +277,7 @@ async function updateBuildingData(
console.log(update); console.log(update);
const patches = compare(oldBuilding, update); const patches = compare(oldBuilding, update);
console.log('Patching', buildingId, patches) console.log('Patching', buildingId, patches);
const [forward, reverse] = patches; const [forward, reverse] = patches;
if (Object.keys(forward).length === 0) { if (Object.keys(forward).length === 0) {
throw 'No change provided'; throw 'No change provided';
@ -336,7 +337,7 @@ function privateQueryBuildingBBOX(buildingId: number){
} }
async function expireBuildingTileCache(buildingId: number) { async function expireBuildingTileCache(buildingId: number) {
const bbox = await privateQueryBuildingBBOX(buildingId) const bbox = await privateQueryBuildingBBOX(buildingId);
const buildingBbox: BoundingBox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin]; const buildingBbox: BoundingBox = [bbox.xmax, bbox.ymax, bbox.xmin, bbox.ymin];
tileCache.removeAllAtBbox(buildingBbox); tileCache.removeAllAtBbox(buildingBbox);
} }

View File

@ -50,12 +50,12 @@ function getDataExtractFromRow(er: DataExtractRow): DataExtract {
extract_id: er.extract_id, extract_id: er.extract_id,
extracted_on: er.extracted_on, extracted_on: er.extracted_on,
download_path: getDownloadLinkForExtract(er) download_path: getDownloadLinkForExtract(er)
} };
} }
function getDownloadLinkForExtract(extract: DataExtractRow): string { function getDownloadLinkForExtract(extract: DataExtractRow): string {
const file_name = path.basename(extract.extract_path); const file_name = path.basename(extract.extract_path);
return `/downloads/${file_name}` // TODO: potentially move base path to env var return `/downloads/${file_name}`; // TODO: potentially move base path to env var
} }
export { export {

View File

@ -1,12 +1,13 @@
import url, { URL } from 'url';
import { errors } from 'pg-promise';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { errors } from 'pg-promise';
import url, { URL } from 'url';
import db from '../../db'; import db from '../../db';
import * as userService from './user';
import { transporter } from './email';
import { validatePassword } from '../validation'; import { validatePassword } from '../validation';
import { transporter } from './email';
import * as userService from './user';
/** /**
* Generate a password reset token for the specified account and send the password reset link by email * Generate a password reset token for the specified account and send the password reset link by email

View File

@ -3,10 +3,10 @@
* *
*/ */
import { errors } from 'pg-promise'; import { errors } from 'pg-promise';
import { promisify } from 'util';
import db from '../../db'; import db from '../../db';
import { validateUsername, ValidationError, validatePassword } from '../validation'; import { validatePassword, validateUsername, ValidationError } from '../validation';
import { promisify } from 'util';
async function createUser(user) { async function createUser(user) {
@ -71,9 +71,9 @@ async function authUser(username: string, password: string) {
); );
if (user && user.auth_ok) { if (user && user.auth_ok) {
return { user_id: user.user_id } return { user_id: user.user_id };
} else { } else {
return { error: 'Username or password not recognised' } return { error: 'Username or password not recognised' };
} }
} catch(err) { } catch(err) {
if (err instanceof errors.QueryResultError) { if (err instanceof errors.QueryResultError) {
@ -99,7 +99,7 @@ async function getUserById(id: string) {
] ]
); );
} catch(error) { } catch(error) {
console.error('Error:', error) console.error('Error:', error);
return undefined; return undefined;
} }
} }
@ -137,7 +137,7 @@ async function getNewUserAPIKey(id: string) {
] ]
); );
} catch(error) { } catch(error) {
console.error('Error:', error) console.error('Error:', error);
return { error: 'Failed to generate new API key.' }; return { error: 'Failed to generate new API key.' };
} }
} }
@ -156,7 +156,7 @@ async function authAPIUser(key: string) {
] ]
); );
} catch(error) { } catch(error) {
console.error('Error:', error) console.error('Error:', error);
return undefined; return undefined;
} }
} }

View File

@ -2,9 +2,9 @@
* Client-side entry point to shared frontend React App * Client-side entry point to shared frontend React App
* *
*/ */
import { BrowserRouter } from 'react-router-dom';
import React from 'react'; import React from 'react';
import { hydrate } from 'react-dom'; import { hydrate } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './frontend/app'; import App from './frontend/app';

View File

@ -1,29 +1,27 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Route, Switch, Link } from 'react-router-dom'; import { Link, Route, Switch } from 'react-router-dom';
import Header from './header';
import MapApp from './map-app';
import { Building } from './models/building';
import { User } from './models/user';
import AboutPage from './pages/about';
import ContactPage from './pages/contact';
import ContributorAgreementPage from './pages/contributor-agreement';
import DataAccuracyPage from './pages/data-accuracy';
import DataExtracts from './pages/data-extracts';
import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence';
import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn';
import PrivacyPolicyPage from './pages/privacy-policy';
import ForgottenPassword from './user/forgotten-password';
import Login from './user/login';
import MyAccountPage from './user/my-account';
import PasswordReset from './user/password-reset';
import SignUp from './user/signup';
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
import './app.css'; import './app.css';
import Header from './header';
import AboutPage from './pages/about';
import ContributorAgreementPage from './pages/contributor-agreement';
import PrivacyPolicyPage from './pages/privacy-policy';
import DataExtracts from './pages/data-extracts';
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 MapApp from './map-app';
import ContactPage from './pages/contact';
import DataAccuracyPage from './pages/data-accuracy';
import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence';
import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn';
import { Building } from './models/building';
import { User } from './models/user';
interface AppProps { interface AppProps {
user?: User; user?: User;

View File

@ -5,7 +5,7 @@ import InfoBox from '../components/info-box';
interface BuildingNotFoundProps { interface BuildingNotFoundProps {
mode: string mode: string;
} }
const BuildingNotFound: React.FunctionComponent<BuildingNotFoundProps> = (props) => ( const BuildingNotFound: React.FunctionComponent<BuildingNotFoundProps> = (props) => (

View File

@ -1,21 +1,21 @@
import React from 'react'; import React from 'react';
import BuildingNotFound from './building-not-found';
import LocationContainer from './data-containers/location';
import UseContainer from './data-containers/use';
import TypeContainer from './data-containers/type';
import AgeContainer from './data-containers/age';
import SizeContainer from './data-containers/size';
import ConstructionContainer from './data-containers/construction';
import TeamContainer from './data-containers/team';
import SustainabilityContainer from './data-containers/sustainability';
import StreetscapeContainer from './data-containers/streetscape';
import CommunityContainer from './data-containers/community';
import PlanningContainer from './data-containers/planning';
import LikeContainer from './data-containers/like';
import { Building } from '../models/building'; import { Building } from '../models/building';
import BuildingNotFound from './building-not-found';
import AgeContainer from './data-containers/age';
import CommunityContainer from './data-containers/community';
import ConstructionContainer from './data-containers/construction';
import LikeContainer from './data-containers/like';
import LocationContainer from './data-containers/location';
import PlanningContainer from './data-containers/planning';
import SizeContainer from './data-containers/size';
import StreetscapeContainer from './data-containers/streetscape';
import SustainabilityContainer from './data-containers/sustainability';
import TeamContainer from './data-containers/team';
import TypeContainer from './data-containers/type';
import UseContainer from './data-containers/use';
interface BuildingViewProps { interface BuildingViewProps {
cat: string; cat: string;
@ -23,7 +23,7 @@ interface BuildingViewProps {
building?: Building; building?: Building;
building_like?: boolean; building_like?: boolean;
user?: any; user?: any;
selectBuilding: (building: Building) => void selectBuilding: (building: Building) => void;
} }
/** /**
@ -39,7 +39,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
title="Location" title="Location"
help="https://pages.colouring.london/location" help="https://pages.colouring.london/location"
intro="Where are the buildings? Address, location and cross-references." intro="Where are the buildings? Address, location and cross-references."
/> />;
case 'use': case 'use':
return <UseContainer return <UseContainer
{...props} {...props}
@ -47,7 +47,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
title="Land Use" title="Land Use"
intro="How are buildings used, and how does use change over time? Coming soon…" intro="How are buildings used, and how does use change over time? Coming soon…"
help="https://pages.colouring.london/use" help="https://pages.colouring.london/use"
/> />;
case 'type': case 'type':
return <TypeContainer return <TypeContainer
{...props} {...props}
@ -55,21 +55,21 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
title="Type" title="Type"
intro="How were buildings previously used?" intro="How were buildings previously used?"
help="https://www.pages.colouring.london/buildingtypology" help="https://www.pages.colouring.london/buildingtypology"
/> />;
case 'age': case 'age':
return <AgeContainer return <AgeContainer
{...props} {...props}
title="Age" title="Age"
help="https://pages.colouring.london/age" help="https://pages.colouring.london/age"
intro="Building age data can support energy analysis and help predict long-term change." intro="Building age data can support energy analysis and help predict long-term change."
/> />;
case 'size': case 'size':
return <SizeContainer return <SizeContainer
{...props} {...props}
title="Size &amp; Shape" title="Size &amp; Shape"
intro="How big are buildings?" intro="How big are buildings?"
help="https://pages.colouring.london/shapeandsize" help="https://pages.colouring.london/shapeandsize"
/> />;
case 'construction': case 'construction':
return <ConstructionContainer return <ConstructionContainer
{...props} {...props}
@ -77,7 +77,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
intro="How are buildings built? Coming soon…" intro="How are buildings built? Coming soon…"
help="https://pages.colouring.london/construction" help="https://pages.colouring.london/construction"
inactive={true} inactive={true}
/> />;
case 'team': case 'team':
return <TeamContainer return <TeamContainer
{...props} {...props}
@ -85,7 +85,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
intro="Who built the buildings? Coming soon…" intro="Who built the buildings? Coming soon…"
help="https://pages.colouring.london/team" help="https://pages.colouring.london/team"
inactive={true} inactive={true}
/> />;
case 'sustainability': case 'sustainability':
return <SustainabilityContainer return <SustainabilityContainer
{...props} {...props}
@ -93,7 +93,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
intro="Are buildings energy efficient?" intro="Are buildings energy efficient?"
help="https://pages.colouring.london/sustainability" help="https://pages.colouring.london/sustainability"
inactive={false} inactive={false}
/> />;
case 'streetscape': case 'streetscape':
return <StreetscapeContainer return <StreetscapeContainer
{...props} {...props}
@ -101,7 +101,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
intro="What's the building's context? Coming soon…" intro="What's the building's context? Coming soon…"
help="https://pages.colouring.london/streetscape" help="https://pages.colouring.london/streetscape"
inactive={true} inactive={true}
/> />;
case 'community': case 'community':
return <CommunityContainer return <CommunityContainer
{...props} {...props}
@ -109,24 +109,24 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
intro="How does this building work for the local community?" intro="How does this building work for the local community?"
help="https://pages.colouring.london/community" help="https://pages.colouring.london/community"
inactive={true} inactive={true}
/> />;
case 'planning': case 'planning':
return <PlanningContainer return <PlanningContainer
{...props} {...props}
title="Planning" title="Planning"
intro="Planning controls relating to protection and reuse." intro="Planning controls relating to protection and reuse."
help="https://pages.colouring.london/planning" help="https://pages.colouring.london/planning"
/> />;
case 'like': case 'like':
return <LikeContainer return <LikeContainer
{...props} {...props}
title="Like Me!" title="Like Me!"
intro="Do you like the building and think it contributes to the city?" intro="Do you like the building and think it contributes to the city?"
help="https://pages.colouring.london/likeme" help="https://pages.colouring.london/likeme"
/> />;
default: default:
return <BuildingNotFound mode="view" /> return <BuildingNotFound mode="view" />;
} }
} };
export default BuildingView; export default BuildingView;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import './categories.css' import './categories.css';
interface CategoriesProps { interface CategoriesProps {
mode: 'view' | 'edit' | 'multi-edit'; mode: 'view' | 'edit' | 'multi-edit';
@ -119,7 +119,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
building_id={props.building_id} building_id={props.building_id}
/> />
</ol> </ol>
) );
interface CategoryProps { interface CategoryProps {
mode: 'view' | 'edit' | 'multi-edit'; mode: 'view' | 'edit' | 'multi-edit';
@ -152,6 +152,6 @@ const Category: React.FC<CategoryProps> = (props) => {
</NavLink> </NavLink>
</li> </li>
); );
} };
export default Categories; export default Categories;

View File

@ -19,6 +19,6 @@ const ContainerHeader: React.FunctionComponent<ContainerHeaderProps> = (props) =
{props.children} {props.children}
</nav> </nav>
</header> </header>
) );
export default ContainerHeader; export default ContainerHeader;

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
import { DataTitleCopyable } from './data-title';
interface CheckboxDataEntryProps extends BaseDataEntryProps { interface CheckboxDataEntryProps extends BaseDataEntryProps {
value: boolean; value: boolean;
@ -33,6 +33,6 @@ const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (prop
</div> </div>
</Fragment> </Fragment>
); );
} };
export default CheckboxDataEntry; export default CheckboxDataEntry;

View File

@ -1,7 +1,8 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import { DownIcon, RightIcon } from "../../components/icons";
import './data-entry-group.css'; import './data-entry-group.css';
import { RightIcon, DownIcon } from "../../components/icons";
interface DataEntryGroupProps { interface DataEntryGroupProps {
/** Name of the group */ /** Name of the group */
@ -29,7 +30,7 @@ const DataEntryGroup: React.FunctionComponent<DataEntryGroupProps> = (props) =>
</div> </div>
</Fragment> </Fragment>
); );
} };
const CollapseIcon: React.FunctionComponent<{collapsed: boolean}> = (props) => ( const CollapseIcon: React.FunctionComponent<{collapsed: boolean}> = (props) => (
<span className="collapse-icon"> <span className="collapse-icon">

View File

@ -1,8 +1,9 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { DataTitleCopyable } from './data-title';
import { CopyProps } from '../data-containers/category-view-props'; import { CopyProps } from '../data-containers/category-view-props';
import { DataTitleCopyable } from './data-title';
interface BaseDataEntryProps { interface BaseDataEntryProps {
slug: string; slug: string;
title: string; title: string;
@ -17,7 +18,7 @@ interface DataEntryProps extends BaseDataEntryProps {
value?: string; value?: string;
maxLength?: number; maxLength?: number;
placeholder?: string; placeholder?: string;
valueTransform?: (string) => string valueTransform?: (string) => string;
} }
const DataEntry: React.FunctionComponent<DataEntryProps> = (props) => { const DataEntry: React.FunctionComponent<DataEntryProps> = (props) => {
@ -47,7 +48,7 @@ const DataEntry: React.FunctionComponent<DataEntryProps> = (props) => {
/> />
</Fragment> </Fragment>
); );
} };
export default DataEntry; export default DataEntry;
export { export {

View File

@ -15,8 +15,8 @@ const DataTitle: React.FunctionComponent<DataTitleProps> = (props) => {
{ props.title } { props.title }
{ props.tooltip? <Tooltip text={ props.tooltip } /> : null } { props.tooltip? <Tooltip text={ props.tooltip } /> : null }
</dt> </dt>
) );
} };
interface DataTitleCopyableProps { interface DataTitleCopyableProps {
@ -48,7 +48,7 @@ const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (prop
</label> </label>
</div> </div>
); );
} };
export default DataTitle; export default DataTitle;
export { DataTitleCopyable } export { DataTitleCopyable };

View File

@ -46,6 +46,6 @@ const LikeDataEntry: React.FunctionComponent<LikeDataEntryProps> = (props) => {
</label> </label>
</Fragment> </Fragment>
); );
} };
export default LikeDataEntry; export default LikeDataEntry;

View File

@ -1,8 +1,9 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { sanitiseURL } from '../../helpers'; import { sanitiseURL } from '../../helpers';
import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
import { DataTitleCopyable } from './data-title';
interface MultiDataEntryProps extends BaseDataEntryProps { interface MultiDataEntryProps extends BaseDataEntryProps {
@ -68,7 +69,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps> {
key={index} key={index}
className="form-control"> className="form-control">
<a href={sanitiseURL(item)}>{item}</a> <a href={sanitiseURL(item)}>{item}</a>
</li> </li>;
}) })
} }
</ul> </ul>
@ -97,7 +98,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps> {
onClick={this.add} onClick={this.add}
disabled={props.mode === 'view'} disabled={props.mode === 'view'}
className="btn btn-outline-dark">+</button> className="btn btn-outline-dark">+</button>
</Fragment> </Fragment>;
} }
} }

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
import { DataTitleCopyable } from './data-title';
interface NumericDataEntryProps extends BaseDataEntryProps { interface NumericDataEntryProps extends BaseDataEntryProps {
@ -42,6 +42,6 @@ const NumericDataEntry: React.FunctionComponent<NumericDataEntryProps> = (props)
/> />
</Fragment> </Fragment>
); );
} };
export default NumericDataEntry; export default NumericDataEntry;

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
import { DataTitleCopyable } from './data-title';
interface SelectDataEntryProps extends BaseDataEntryProps { interface SelectDataEntryProps extends BaseDataEntryProps {
value: string; value: string;
@ -41,6 +41,6 @@ const SelectDataEntry: React.FunctionComponent<SelectDataEntryProps> = (props) =
</select> </select>
</Fragment> </Fragment>
); );
} };
export default SelectDataEntry; export default SelectDataEntry;

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
import { DataTitleCopyable } from './data-title';
interface TextboxDataEntryProps extends BaseDataEntryProps { interface TextboxDataEntryProps extends BaseDataEntryProps {
value: string; value: string;
@ -39,6 +39,6 @@ const TextboxDataEntry: React.FunctionComponent<TextboxDataEntryProps> = (props)
></textarea> ></textarea>
</Fragment> </Fragment>
); );
} };
export default TextboxDataEntry; export default TextboxDataEntry;

View File

@ -53,7 +53,7 @@ const UPRNsDataEntry: React.FC<UPRNsDataEntryProps> = (props) => {
} }
</dd> </dd>
</Fragment> </Fragment>
) );
}; };
export default UPRNsDataEntry; export default UPRNsDataEntry;

View File

@ -1,9 +1,10 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import NumericDataEntry from './numeric-data-entry';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import { CopyProps } from '../data-containers/category-view-props'; import { CopyProps } from '../data-containers/category-view-props';
import NumericDataEntry from './numeric-data-entry';
interface YearDataEntryProps { interface YearDataEntryProps {
year: number; year: number;
upper: number; upper: number;
@ -22,7 +23,7 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
lower: props.lower, lower: props.lower,
decade: Math.floor(props.year / 10) * 10, decade: Math.floor(props.year / 10) * 10,
century: Math.floor(props.year / 100) * 100 century: Math.floor(props.year / 100) * 100
} };
} }
// TODO add dropdown for decade, century // TODO add dropdown for decade, century
// TODO roll in first/last year estimate // TODO roll in first/last year estimate
@ -61,7 +62,7 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
tooltip={dataFields.date_lower.tooltip} tooltip={dataFields.date_lower.tooltip}
/> />
</Fragment> </Fragment>
) );
} }
} }

View File

@ -1,15 +1,16 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Redirect, NavLink } from 'react-router-dom'; import { NavLink, Redirect } from 'react-router-dom';
import ContainerHeader from './container-header';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
import { CopyControl } from './header-buttons/copy-control'; import { compareObjects } from '../helpers';
import { ViewEditControl } from './header-buttons/view-edit-control';
import { Building } from '../models/building'; import { Building } from '../models/building';
import { User } from '../models/user'; import { User } from '../models/user';
import { compareObjects } from '../helpers';
import ContainerHeader from './container-header';
import { CategoryViewProps, CopyProps } from './data-containers/category-view-props'; import { CategoryViewProps, CopyProps } from './data-containers/category-view-props';
import { CopyControl } from './header-buttons/copy-control';
import { ViewEditControl } from './header-buttons/view-edit-control';
interface DataContainerProps { interface DataContainerProps {
title: string; title: string;
@ -22,7 +23,7 @@ interface DataContainerProps {
mode: 'view' | 'edit'; mode: 'view' | 'edit';
building?: Building; building?: Building;
building_like?: boolean; building_like?: boolean;
selectBuilding: (building: Building) => void selectBuilding: (building: Building) => void;
} }
interface DataContainerState { interface DataContainerState {
@ -88,7 +89,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
toggleCopying() { toggleCopying() {
this.setState({ this.setState({
copying: !this.state.copying copying: !this.state.copying
}) });
} }
/** /**
@ -105,7 +106,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
} }
this.setState({ this.setState({
keys_to_copy: keys keys_to_copy: keys
}) });
} }
isEdited() { isEdited() {
@ -172,7 +173,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
const data = await res.json(); const data = await res.json();
if (data.error) { if (data.error) {
this.setState({error: data.error}) this.setState({error: data.error});
} else { } else {
this.props.selectBuilding(data); this.props.selectBuilding(data);
this.updateBuildingState('likes_total', data.likes_total); this.updateBuildingState('likes_total', data.likes_total);
@ -198,7 +199,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
const data = await res.json(); const data = await res.json();
if (data.error) { if (data.error) {
this.setState({error: data.error}) this.setState({error: data.error});
} else { } else {
this.props.selectBuilding(data); this.props.selectBuilding(data);
} }
@ -209,14 +210,14 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
render() { render() {
if (this.props.mode === 'edit' && !this.props.user){ if (this.props.mode === 'edit' && !this.props.user){
return <Redirect to="/sign-up.html" /> return <Redirect to="/sign-up.html" />;
} }
const currentBuilding = this.getEditedBuilding(); const currentBuilding = this.getEditedBuilding();
const values_to_copy = {} const values_to_copy = {};
for (const key of Object.keys(this.state.keys_to_copy)) { for (const key of Object.keys(this.state.keys_to_copy)) {
values_to_copy[key] = currentBuilding[key] values_to_copy[key] = currentBuilding[key];
} }
const data_string = JSON.stringify(values_to_copy); const data_string = JSON.stringify(values_to_copy);
const copy: CopyProps = { const copy: CopyProps = {
@ -224,7 +225,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
toggleCopying: this.toggleCopying, toggleCopying: this.toggleCopying,
toggleCopyAttribute: this.toggleCopyAttribute, toggleCopyAttribute: this.toggleCopyAttribute,
copyingKey: (key: string) => this.state.keys_to_copy[key] copyingKey: (key: string) => this.state.keys_to_copy[key]
} };
const headerBackLink = `/${this.props.mode}/categories${this.props.building != undefined ? `/${this.props.building.building_id}` : ''}`; const headerBackLink = `/${this.props.mode}/categories${this.props.building != undefined ? `/${this.props.building.building_id}` : ''}`;
const edited = this.isEdited(); const edited = this.isEdited();
@ -346,7 +347,7 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
</section> </section>
); );
} }
} };
} };
export default withCopyEdit; export default withCopyEdit;

View File

@ -1,12 +1,13 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import { dataFields } from '../../data_fields';
import MultiDataEntry from '../data-components/multi-data-entry'; import MultiDataEntry from '../data-components/multi-data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry'; import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry'; import SelectDataEntry from '../data-components/select-data-entry';
import TextboxDataEntry from '../data-components/textbox-data-entry'; import TextboxDataEntry from '../data-components/textbox-data-entry';
import YearDataEntry from '../data-components/year-data-entry'; import YearDataEntry from '../data-components/year-data-entry';
import { dataFields } from '../../data_fields'; import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -74,7 +75,7 @@ const AgeView: React.FunctionComponent<CategoryViewProps> = (props) => (
placeholder="https://..." placeholder="https://..."
/> />
</Fragment> </Fragment>
) );
const AgeContainer = withCopyEdit(AgeView); const AgeContainer = withCopyEdit(AgeView);
export default AgeContainer; export default AgeContainer;

View File

@ -1,6 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -27,7 +28,7 @@ const CommunityView: React.FunctionComponent<CategoryViewProps> = (props) => (
} }
</ul> </ul>
</Fragment> </Fragment>
) );
const CommunityContainer = withCopyEdit(CommunityView); const CommunityContainer = withCopyEdit(CommunityView);
export default CommunityContainer; export default CommunityContainer;

View File

@ -1,7 +1,6 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
/** /**
* Construction view/edit section * Construction view/edit section
@ -50,7 +49,7 @@ const ConstructionView = (props) => (
} }
</ul> </ul>
</Fragment> </Fragment>
) );
const ConstructionContainer = withCopyEdit(ConstructionView); const ConstructionContainer = withCopyEdit(ConstructionView);
export default ConstructionContainer; export default ConstructionContainer;

View File

@ -1,7 +1,8 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import LikeDataEntry from '../data-components/like-data-entry'; import LikeDataEntry from '../data-components/like-data-entry';
import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -16,7 +17,7 @@ const LikeView: React.FunctionComponent<CategoryViewProps> = (props) => (
onLike={props.onLike} onLike={props.onLike}
/> />
</Fragment> </Fragment>
) );
const LikeContainer = withCopyEdit(LikeView); const LikeContainer = withCopyEdit(LikeView);
export default LikeContainer; export default LikeContainer;

View File

@ -1,11 +1,12 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import InfoBox from '../../components/info-box';
import { dataFields } from '../../data_fields';
import DataEntry from '../data-components/data-entry'; import DataEntry from '../data-components/data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry'; import NumericDataEntry from '../data-components/numeric-data-entry';
import UPRNsDataEntry from '../data-components/uprns-data-entry'; import UPRNsDataEntry from '../data-components/uprns-data-entry';
import InfoBox from '../../components/info-box'; import withCopyEdit from '../data-container';
import { dataFields } from '../../data_fields';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => ( const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => (
@ -113,7 +114,7 @@ const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => (
onChange={props.onChange} onChange={props.onChange}
/> />
</Fragment> </Fragment>
) );
const LocationContainer = withCopyEdit(LocationView); const LocationContainer = withCopyEdit(LocationView);
export default LocationContainer; export default LocationContainer;

View File

@ -1,11 +1,12 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import CheckboxDataEntry from '../data-components/checkbox-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import { DataEntryGroup } from '../data-components/data-entry-group';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import CheckboxDataEntry from '../data-components/checkbox-data-entry';
import DataEntry from '../data-components/data-entry';
import { DataEntryGroup } from '../data-components/data-entry-group';
import SelectDataEntry from '../data-components/select-data-entry';
import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -202,7 +203,7 @@ const PlanningView: React.FunctionComponent<CategoryViewProps> = (props) => (
/> />
</DataEntryGroup> </DataEntryGroup>
</Fragment> </Fragment>
) );
const PlanningContainer = withCopyEdit(PlanningView); const PlanningContainer = withCopyEdit(PlanningView);
export default PlanningContainer export default PlanningContainer;

View File

@ -1,10 +1,11 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import { dataFields } from '../../data_fields';
import { DataEntryGroup } from '../data-components/data-entry-group';
import NumericDataEntry from '../data-components/numeric-data-entry'; import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry'; import SelectDataEntry from '../data-components/select-data-entry';
import { DataEntryGroup } from '../data-components/data-entry-group'; import withCopyEdit from '../data-container';
import { dataFields } from '../../data_fields';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -146,7 +147,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
]} ]}
/> />
</Fragment> </Fragment>
) );
const SizeContainer = withCopyEdit(SizeView); const SizeContainer = withCopyEdit(SizeView);
export default SizeContainer; export default SizeContainer;

View File

@ -1,6 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -18,7 +19,7 @@ const StreetscapeView: React.FunctionComponent<CategoryViewProps> = (props) => (
<li>Building shading</li> <li>Building shading</li>
</ul> </ul>
</Fragment> </Fragment>
) );
const StreetscapeContainer = withCopyEdit(StreetscapeView); const StreetscapeContainer = withCopyEdit(StreetscapeView);
export default StreetscapeContainer; export default StreetscapeContainer;

View File

@ -1,10 +1,10 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
const EnergyCategoryOptions = ["A", "B", "C", "D", "E", "F", "G"]; const EnergyCategoryOptions = ["A", "B", "C", "D", "E", "F", "G"];
@ -78,7 +78,7 @@ const SustainabilityView: React.FunctionComponent<CategoryViewProps> = (props) =
/> />
</Fragment> </Fragment>
); );
} };
const SustainabilityContainer = withCopyEdit(SustainabilityView); const SustainabilityContainer = withCopyEdit(SustainabilityView);
export default SustainabilityContainer; export default SustainabilityContainer;

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import withCopyEdit from '../data-container';
import DataEntry from '../data-components/data-entry';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -25,7 +25,7 @@ const TeamView: React.FunctionComponent<CategoryViewProps> = (props) => (
} }
</ul> </ul>
</Fragment> </Fragment>
) );
const TeamContainer = withCopyEdit(TeamView); const TeamContainer = withCopyEdit(TeamView);
export default TeamContainer; export default TeamContainer;

View File

@ -1,10 +1,11 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container';
import SelectDataEntry from '../data-components/select-data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import DataEntry from '../data-components/data-entry';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import DataEntry from '../data-components/data-entry';
import NumericDataEntry from '../data-components/numeric-data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
const AttachmentFormOptions = [ const AttachmentFormOptions = [
@ -54,7 +55,7 @@ const TypeView: React.FunctionComponent<CategoryViewProps> = (props) => {
/> />
</Fragment> </Fragment>
); );
} };
const TypeContainer = withCopyEdit(TypeView); const TypeContainer = withCopyEdit(TypeView);
export default TypeContainer; export default TypeContainer;

View File

@ -1,6 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import withCopyEdit from '../data-container'; import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props'; import { CategoryViewProps } from './category-view-props';
/** /**
@ -31,7 +32,7 @@ const UseView: React.FunctionComponent<CategoryViewProps> = (props) => (
} }
</ul> </ul>
</Fragment> </Fragment>
) );
const UseContainer = withCopyEdit(UseView); const UseContainer = withCopyEdit(UseView);
export default UseContainer; export default UseContainer;

View File

@ -1,13 +1,15 @@
import React from 'react'; import React from 'react';
import { EditHistoryEntry } from '../models/edit-history-entry';
import { arrayToDictionary, parseDate } from '../../helpers';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import { arrayToDictionary, parseDate } from '../../helpers';
import { EditHistoryEntry } from '../../models/edit-history-entry';
import { CategoryEditSummary } from './category-edit-summary'; import { CategoryEditSummary } from './category-edit-summary';
import './building-edit-summary.css'; import './building-edit-summary.css';
interface BuildingEditSummaryProps { interface BuildingEditSummaryProps {
historyEntry: EditHistoryEntry historyEntry: EditHistoryEntry;
} }
function formatDate(dt: Date) { function formatDate(dt: Date) {
@ -45,7 +47,7 @@ const BuildingEditSummary: React.FunctionComponent<BuildingEditSummaryProps> = p
} }
</div> </div>
); );
} };
export { export {
BuildingEditSummary BuildingEditSummary

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import './category-edit-summary.css';
import { FieldEditSummary } from './field-edit-summary'; import { FieldEditSummary } from './field-edit-summary';
import './category-edit-summary.css';
interface CategoryEditSummaryProps { interface CategoryEditSummaryProps {
category: string; category: string;
fields: { fields: {

View File

@ -1,10 +1,12 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { EditHistoryEntry } from '../models/edit-history-entry';
import { Building } from '../../models/building';
import { EditHistoryEntry } from '../../models/edit-history-entry';
import ContainerHeader from '../container-header';
import { BuildingEditSummary } from './building-edit-summary'; import { BuildingEditSummary } from './building-edit-summary';
import './edit-history.css'; import './edit-history.css';
import { Building } from '../../models/building';
import ContainerHeader from '../container-header';
interface EditHistoryProps { interface EditHistoryProps {
building: Building; building: Building;
@ -39,7 +41,7 @@ const EditHistory: React.FunctionComponent<EditHistoryProps> = (props) => {
</ul> </ul>
</> </>
); );
} };
export { export {
EditHistory EditHistory

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { Building } from '../../models/building';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { ViewIcon, EditIcon } from '../../components/icons';
import { EditIcon, ViewIcon } from '../../components/icons';
import { Building } from '../../models/building';
interface ViewEditControlProps { interface ViewEditControlProps {
cat: string; cat: string;

View File

@ -1,14 +1,15 @@
import { parse } from 'query-string';
import React from 'react'; import React from 'react';
import { Link, Redirect, RouteComponentProps } from 'react-router-dom'; import { Link, Redirect, RouteComponentProps } from 'react-router-dom';
import { parse } from 'query-string';
import Sidebar from './sidebar'; import { BackIcon } from '../components/icons';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
import { BackIcon }from '../components/icons';
import DataEntry from './data-components/data-entry';
import { dataFields } from '../data_fields'; import { dataFields } from '../data_fields';
import { User } from '../models/user'; import { User } from '../models/user';
import DataEntry from './data-components/data-entry';
import Sidebar from './sidebar';
interface MultiEditRouteParams { interface MultiEditRouteParams {
cat: string; cat: string;
} }
@ -19,7 +20,7 @@ interface MultiEditProps extends RouteComponentProps<MultiEditRouteParams> {
const MultiEdit: React.FC<MultiEditProps> = (props) => { const MultiEdit: React.FC<MultiEditProps> = (props) => {
if (!props.user){ if (!props.user){
return <Redirect to="/sign-up.html" /> return <Redirect to="/sign-up.html" />;
} }
const cat = props.match.params.cat; const cat = props.match.params.cat;
if (cat === 'like') { if (cat === 'like') {
@ -45,14 +46,14 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
let data: object; let data: object;
if (cat === 'like'){ if (cat === 'like'){
data = { like: true } data = { like: true };
} else { } else {
try { try {
// TODO: verify what happens if data is string[] // TODO: verify what happens if data is string[]
data = JSON.parse(q.data as string); data = JSON.parse(q.data as string);
} catch (error) { } catch (error) {
console.error(error, q) console.error(error, q);
data = {} data = {};
} }
} }
@ -79,7 +80,7 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
disabled={true} disabled={true}
value={data[key]} value={data[key]}
/> />
) );
})) }))
} }
</form> </form>
@ -90,6 +91,6 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
</section> </section>
</Sidebar> </Sidebar>
); );
} };
export default MultiEdit; export default MultiEdit;

View File

@ -3,14 +3,14 @@ import React from 'react';
import './confirmation-modal.css'; import './confirmation-modal.css';
interface ConfirmationModalProps { interface ConfirmationModalProps {
show: boolean, show: boolean;
title: string, title: string;
description: string, description: string;
confirmButtonText?: string, confirmButtonText?: string;
confirmButtonClass?: string, confirmButtonClass?: string;
cancelButtonClass?: string, cancelButtonClass?: string;
onConfirm: () => void, onConfirm: () => void;
onCancel: () => void onCancel: () => void;
} }
const ConfirmationModal: React.FunctionComponent<ConfirmationModalProps> = ({ const ConfirmationModal: React.FunctionComponent<ConfirmationModalProps> = ({

View File

@ -1,11 +1,11 @@
/** /**
* Mini-library of icons * Mini-library of icons
*/ */
import React from 'react' import { library } from '@fortawesome/fontawesome-svg-core';
import { library } from '@fortawesome/fontawesome-svg-core' import { faAngleLeft, faCaretDown, faCaretRight, faCaretUp, faCheck, faCheckDouble,
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' faEye, faInfoCircle, faPaintBrush, faQuestionCircle, faSearch, faTimes } from '@fortawesome/free-solid-svg-icons';
import { faQuestionCircle, faPaintBrush, faInfoCircle, faTimes, faCheck, faCheckDouble, import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
faAngleLeft, faCaretDown, faSearch, faEye, faCaretUp, faCaretRight } from '@fortawesome/free-solid-svg-icons' import React from 'react';
library.add( library.add(
faQuestionCircle, faQuestionCircle,

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import './logo.css'; import './logo.css';
interface LogoProps { interface LogoProps {
@ -44,6 +45,6 @@ const LogoGrid: React.FunctionComponent = () => (
<div className="cell background-like"></div> <div className="cell background-like"></div>
</div> </div>
</div> </div>
) );
export { Logo }; export { Logo };

View File

@ -1,8 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import './tooltip.css';
import { InfoIcon } from './icons'; import { InfoIcon } from './icons';
import './tooltip.css';
interface TooltipProps { interface TooltipProps {
text: string; text: string;
} }

View File

@ -1,27 +1,27 @@
import urlapi from 'url'; import urlapi from 'url';
function sanitiseURL(string){ function sanitiseURL(string){
let url_ let url_;
// http or https // http or https
if (!(string.substring(0, 7) === 'http://' || string.substring(0, 8) === 'https://')){ if (!(string.substring(0, 7) === 'http://' || string.substring(0, 8) === 'https://')){
return null return null;
} }
try { try {
url_ = document.createElement('a') url_ = document.createElement('a');
url_.href = string url_.href = string;
} catch (error) { } catch (error) {
try { try {
url_ = urlapi.parse(string) url_ = urlapi.parse(string);
} catch (error) { } catch (error) {
return null return null;
} }
} }
// required (www.example.com) // required (www.example.com)
if (!url_.hostname || url_.hostname === '' || url_.hostname === 'localhost'){ if (!url_.hostname || url_.hostname === '' || url_.hostname === 'localhost'){
return null return null;
} }
// optional (/some/path) // optional (/some/path)
@ -33,7 +33,7 @@ function sanitiseURL(string){
// optional (#anchor) // optional (#anchor)
// url_.hash; // url_.hash;
return `${url_.protocol}//${url_.hostname}${url_.pathname || ''}${url_.search || ''}${url_.hash || ''}` return `${url_.protocol}//${url_.hostname}${url_.pathname || ''}${url_.search || ''}${url_.hash || ''}`;
} }
/** /**
@ -63,8 +63,8 @@ function parseDate(isoUtcDate: string): Date {
} }
function compareObjects(objA: object, objB: object): [object, object] { function compareObjects(objA: object, objB: object): [object, object] {
const reverse = {} const reverse = {};
const forward = {} const forward = {};
for (const [key, value] of Object.entries(objB)) { for (const [key, value] of Object.entries(objB)) {
if (objA[key] !== value) { if (objA[key] !== value) {
reverse[key] = objA[key]; reverse[key] = objA[key];

View File

@ -1,15 +1,15 @@
import React, { Fragment } from 'react';
import { Switch, Route, RouteComponentProps, Redirect } from 'react-router-dom';
import Welcome from './pages/welcome';
import Sidebar from './building/sidebar';
import Categories from './building/categories';
import MultiEdit from './building/multi-edit';
import BuildingView from './building/building-view';
import ColouringMap from './map/map';
import { parse } from 'query-string'; import { parse } from 'query-string';
import React, { Fragment } from 'react';
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom';
import BuildingView from './building/building-view';
import Categories from './building/categories';
import { EditHistory } from './building/edit-history/edit-history'; 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 { Building } from './models/building';
import Welcome from './pages/welcome';
interface MapAppRouteParams { interface MapAppRouteParams {
mode: 'view' | 'edit' | 'multi-edit'; mode: 'view' | 'edit' | 'multi-edit';
@ -85,7 +85,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
revisionId = +revisionId; revisionId = +revisionId;
// bump revision id, only ever increasing // bump revision id, only ever increasing
if (revisionId > this.state.revision_id) { if (revisionId > this.state.revision_id) {
this.setState({ revision_id: revisionId }) this.setState({ revision_id: revisionId });
} }
} }
@ -117,7 +117,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
this.setState({ building: building }); this.setState({ building: building });
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
this.setState({ building: building }); this.setState({ building: building });
}); });
@ -138,7 +138,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
this.props.history.push(`/${mode}/${category}/${building.building_id}`); this.props.history.push(`/${mode}/${category}/${building.building_id}`);
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
this.setState({ building_like: false }); this.setState({ building_like: false });
}); });
} }
@ -157,14 +157,14 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
const q = parse(window.location.search); const q = parse(window.location.search);
if (cat === 'like') { if (cat === 'like') {
this.likeBuilding(building.building_id) this.likeBuilding(building.building_id);
} else { } else {
try { try {
// TODO: verify what happens if data is string[] // TODO: verify what happens if data is string[]
const data = JSON.parse(q.data as string); const data = JSON.parse(q.data as string);
this.updateBuilding(building.building_id, data) this.updateBuilding(building.building_id, data);
} catch (error) { } catch (error) {
console.error(error, q) console.error(error, q);
} }
} }
} }
@ -181,7 +181,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
res => res.json() res => res.json()
).then(function (res) { ).then(function (res) {
if (res.error) { if (res.error) {
console.error({ error: res.error }) console.error({ error: res.error });
} else { } else {
this.increaseRevision(res.revision_id); this.increaseRevision(res.revision_id);
} }
@ -202,7 +202,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
res => res.json() res => res.json()
).then(res => { ).then(res => {
if (res.error) { if (res.error) {
console.error({ error: res.error }) console.error({ error: res.error });
} else { } else {
this.increaseRevision(res.revision_id); this.increaseRevision(res.revision_id);
} }

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import './legend.css'; import { DownIcon, UpIcon } from '../components/icons';
import { Logo } from '../components/logo'; import { Logo } from '../components/logo';
import { DownIcon, UpIcon, BackIcon } from '../components/icons';
import './legend.css';
const LEGEND_CONFIG = { const LEGEND_CONFIG = {
location: { location: {

View File

@ -1,15 +1,16 @@
import React, { Component, Fragment } from 'react';
import { Map, TileLayer, ZoomControl, AttributionControl, GeoJSON } from 'react-leaflet-universal';
import { GeoJsonObject } from 'geojson'; import { GeoJsonObject } from 'geojson';
import React, { Component, Fragment } from 'react';
import '../../../node_modules/leaflet/dist/leaflet.css' import { AttributionControl, GeoJSON, Map, TileLayer, ZoomControl } from 'react-leaflet-universal';
import './map.css'
import { HelpIcon } from '../components/icons'; import { HelpIcon } from '../components/icons';
import { Building } from '../models/building';
import Legend from './legend'; import Legend from './legend';
import SearchBox from './search-box'; import SearchBox from './search-box';
import ThemeSwitcher from './theme-switcher'; import ThemeSwitcher from './theme-switcher';
import { Building } from '../models/building';
import '../../../node_modules/leaflet/dist/leaflet.css';
import './map.css';
const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9'; const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9';
@ -83,7 +84,7 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
} }
}.bind(this)).catch( }.bind(this)).catch(
(err) => console.error(err) (err) => console.error(err)
) );
} }
themeSwitch(e) { themeSwitch(e) {

View File

@ -1,8 +1,9 @@
import { Point } from 'geojson';
import React, { Component } from 'react'; import React, { Component } from 'react';
import './search-box.css';
import { SearchIcon } from '../components/icons'; import { SearchIcon } from '../components/icons';
import { Point } from 'geojson';
import './search-box.css';
interface SearchResult { interface SearchResult {
type: string; type: string;
@ -10,12 +11,12 @@ interface SearchResult {
label: string; label: string;
zoom: number; zoom: number;
}; };
geometry: Point geometry: Point;
} }
interface SearchBoxProps { interface SearchBoxProps {
onLocate: (lat: number, lng: number, zoom: number) => void onLocate: (lat: number, lng: number, zoom: number) => void;
} }
interface SearchBoxState { interface SearchBoxState {
@ -40,7 +41,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
collapsedSearch: true, collapsedSearch: true,
//is this a small screen device? if not we will disable collapse option //is this a small screen device? if not we will disable collapse option
smallScreen: false smallScreen: false
} };
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.search = this.search.bind(this); this.search = this.search.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this);
@ -93,7 +94,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
e.preventDefault(); e.preventDefault();
this.setState({ this.setState({
fetching: true fetching: true
}) });
fetch( fetch(
'/api/search?q='+this.state.q '/api/search?q='+this.state.q
@ -104,23 +105,23 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
this.setState({ this.setState({
results: data.results, results: data.results,
fetching: false fetching: false
}) });
} else { } else {
console.error(data); console.error(data);
this.setState({ this.setState({
results: [], results: [],
fetching: false fetching: false
}) });
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
this.setState({ this.setState({
results: [], results: [],
fetching: false fetching: false
}) });
}) });
} }
componentDidMount() { componentDidMount() {
@ -148,7 +149,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
<div className="collapse-btn" onClick={this.expandSearch}> <div className="collapse-btn" onClick={this.expandSearch}>
<SearchIcon /> <SearchIcon />
</div> </div>
) );
} }
const resultsList = this.state.results.length? const resultsList = this.state.results.length?
@ -159,7 +160,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
const lng = result.geometry.coordinates[0]; const lng = result.geometry.coordinates[0];
const lat = result.geometry.coordinates[1]; const lat = result.geometry.coordinates[1];
const zoom = result.attributes.zoom; const zoom = result.attributes.zoom;
const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}` const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}`;
return ( return (
<li key={result.attributes.label}> <li key={result.attributes.label}>
<a <a
@ -171,7 +172,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
href={href} href={href}
>{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a> >{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a>
</li> </li>
) );
}) })
} }
</ul> </ul>
@ -196,7 +197,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
</form> </form>
{ resultsList } { resultsList }
</div> </div>
) );
} }
} }

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import SupporterLogos from '../components/supporter-logos';
import './about.css';
import Categories from '../building/categories'; import Categories from '../building/categories';
import SupporterLogos from '../components/supporter-logos';
import './about.css';
const AboutPage = () => ( const AboutPage = () => (
<article> <article>
@ -140,7 +141,7 @@ const AboutPage = () => (
onSubmit={function() {window.open( onSubmit={function() {window.open(
'https://tinyletter.com/colouringlondon', 'https://tinyletter.com/colouringlondon',
'popupwindow', 'popupwindow',
'scrollbars=yes,width=800,height=600'); return true}}> 'scrollbars=yes,width=800,height=600'); return true;}}>
<h3 className="h1">Keep in touch</h3> <h3 className="h1">Keep in touch</h3>
<p> <p>

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import InfoBox from '../components/info-box';
const ContributorAgreementPage : React.SFC<any> = () => ( const ContributorAgreementPage : React.SFC<any> = () => (
<article> <article>
<section className='main-col'> <section className='main-col'>
@ -40,6 +38,6 @@ const ContributorAgreementPage : React.SFC<any> = () => (
</div> </div>
</section> </section>
</article> </article>
) );
export default ContributorAgreementPage; export default ContributorAgreementPage;

View File

@ -1,7 +1,8 @@
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { Link } from 'react-router-dom';
import { dateReviver } from '../../helpers'; import { dateReviver } from '../../helpers';
import { Link } from 'react-router-dom';
interface ExtractViewModel { interface ExtractViewModel {
extract_id: number; extract_id: number;

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import InfoBox from '../components/info-box';
const PrivacyPolicyPage: React.SFC<any> = () => ( const PrivacyPolicyPage: React.SFC<any> = () => (
<article> <article>
<section className='main-col'> <section className='main-col'>
@ -118,6 +116,6 @@ const PrivacyPolicyPage: React.SFC<any> = () => (
</div> </div>
</section> </section>
</article> </article>
) );
export default PrivacyPolicyPage; export default PrivacyPolicyPage;

View File

@ -1,6 +1,7 @@
import React, { FormEvent, ChangeEvent } from 'react'; import React, { ChangeEvent, FormEvent } from 'react';
import InfoBox from '../components/info-box';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
interface ForgottenPasswordState { interface ForgottenPasswordState {
success: boolean; success: boolean;
@ -79,6 +80,6 @@ export default class ForgottenPassword extends React.Component<{}, ForgottenPass
</form> </form>
</section> </section>
</article> </article>
) );
} }
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
@ -7,7 +7,7 @@ import SupporterLogos from '../components/supporter-logos';
import { User } from '../models/user'; import { User } from '../models/user';
interface LoginProps { interface LoginProps {
user: User, user: User;
login: (user: User) => void; login: (user: User) => void;
} }
@ -37,7 +37,7 @@ class Login extends Component<LoginProps, any> {
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
this.setState({error: undefined}) this.setState({error: undefined});
fetch('/api/login', { fetch('/api/login', {
method: 'POST', method: 'POST',
@ -50,7 +50,7 @@ class Login extends Component<LoginProps, any> {
res => res.json() res => res.json()
).then(function(res){ ).then(function(res){
if (res.error) { if (res.error) {
this.setState({error: res.error}) this.setState({error: res.error});
} else { } else {
fetch('/api/users/me', { fetch('/api/users/me', {
credentials: 'same-origin' credentials: 'same-origin'
@ -58,13 +58,13 @@ class Login extends Component<LoginProps, any> {
(res) => res.json() (res) => res.json()
).then(user => { ).then(user => {
if (user.error) { if (user.error) {
this.setState({error: user.error}) this.setState({error: user.error});
} else { } else {
this.props.login(user) this.props.login(user);
} }
}).catch( }).catch(
(err) => this.setState({error: err}) (err) => this.setState({error: err})
) );
} }
}.bind(this)).catch( }.bind(this)).catch(
(err) => this.setState({error: err}) (err) => this.setState({error: err})
@ -73,7 +73,7 @@ class Login extends Component<LoginProps, any> {
render() { render() {
if (this.props.user && !this.props.user.error) { if (this.props.user && !this.props.user.error) {
return <Redirect to="/my-account.html" /> return <Redirect to="/my-account.html" />;
} }
return ( return (
<article> <article>
@ -130,7 +130,7 @@ class Login extends Component<LoginProps, any> {
<SupporterLogos /> <SupporterLogos />
</section> </section>
</article> </article>
) );
} }
} }

View File

@ -39,7 +39,7 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
res => res.json() res => res.json()
).then(function(res){ ).then(function(res){
if (res.error) { if (res.error) {
this.setState({error: res.error}) this.setState({error: res.error});
} else { } else {
this.props.logout(); this.props.logout();
} }
@ -59,7 +59,7 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
res => res.json() res => res.json()
).then(function(res){ ).then(function(res){
if (res.error) { if (res.error) {
this.setState({error: res.error}) this.setState({error: res.error});
} else { } else {
this.props.updateUser(res); this.props.updateUser(res);
} }
@ -176,7 +176,7 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
} else { } else {
return ( return (
<Redirect to="/login.html" /> <Redirect to="/login.html" />
) );
} }
} }
} }

View File

@ -1,8 +1,8 @@
import React, { FormEvent } from 'react'; import React, { FormEvent } from 'react';
import { RouteComponentProps, Redirect } from 'react-router'; import { Redirect, RouteComponentProps } from 'react-router';
import { Link } from 'react-router-dom';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import { Link } from 'react-router-dom';
interface PasswordResetState { interface PasswordResetState {
error: string; error: string;
@ -117,6 +117,6 @@ export default class PasswordReset extends React.Component<RouteComponentProps,
</form> </form>
</section> </section>
</article> </article>
) );
} }
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
@ -50,7 +50,7 @@ class SignUp extends Component<SignUpProps, SignUpState> {
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
this.setState({error: undefined}) this.setState({error: undefined});
fetch('/api/users', { fetch('/api/users', {
method: 'POST', method: 'POST',
@ -63,7 +63,7 @@ class SignUp extends Component<SignUpProps, SignUpState> {
res => res.json() res => res.json()
).then(function(res){ ).then(function(res){
if (res.error) { if (res.error) {
this.setState({error: res.error}) this.setState({error: res.error});
} else { } else {
fetch('/api/users/me', { fetch('/api/users/me', {
credentials: 'same-origin' credentials: 'same-origin'
@ -73,7 +73,7 @@ class SignUp extends Component<SignUpProps, SignUpState> {
(user) => this.props.login(user) (user) => this.props.login(user)
).catch( ).catch(
(err) => this.setState({error: err}) (err) => this.setState({error: err})
) );
} }
}.bind(this)).catch( }.bind(this)).catch(
(err) => this.setState({error: err}) (err) => this.setState({error: err})
@ -82,7 +82,7 @@ class SignUp extends Component<SignUpProps, SignUpState> {
render() { render() {
if (this.props.user) { if (this.props.user) {
return <Redirect to="/my-account.html" /> return <Redirect to="/my-account.html" />;
} }
return ( return (
<article> <article>
@ -175,7 +175,7 @@ class SignUp extends Component<SignUpProps, SignUpState> {
<SupporterLogos /> <SupporterLogos />
</section> </section>
</article> </article>
) );
} }
} }

View File

@ -1,20 +1,18 @@
import express from 'express'; import express from 'express';
import React from 'react'; import React from 'react';
import { StaticRouter } from 'react-router-dom';
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
import App from './frontend/app';
import { parseBuildingURL } from './parse';
import { getUserById } from './api/services/user';
import { import {
getBuildingById, getBuildingById,
getBuildingLikeById, getBuildingLikeById,
getBuildingUPRNsById, getBuildingUPRNsById,
getLatestRevisionId getLatestRevisionId
} from './api/services/building'; } from './api/services/building';
import { getUserById } from './api/services/user';
import App from './frontend/app';
import { parseBuildingURL } from './parse';
// reference packed assets // reference packed assets

View File

@ -27,7 +27,7 @@ function parseBuildingURL(url) {
const matches = re.exec(url); const matches = re.exec(url);
if (matches && matches.length >= 2) { if (matches && matches.length >= 2) {
return strictParseInt(matches[1]) return strictParseInt(matches[1]);
} }
return undefined; return undefined;
} }

View File

@ -4,15 +4,14 @@
* - entry-point to shared React App * - entry-point to shared React App
* *
*/ */
import express from 'express';
import session from 'express-session';
import pgConnect from 'connect-pg-simple'; import pgConnect from 'connect-pg-simple';
import express from 'express';
import session from 'express-session';
import db from './db';
import tileserver from './tiles/tileserver';
import apiServer from './api/api'; import apiServer from './api/api';
import db from './db';
import frontendRoute from './frontendRoute'; import frontendRoute from './frontendRoute';
import tileserver from './tiles/tileserver';
// create server // create server
const server = express(); const server = express();
@ -38,9 +37,9 @@ const sess: any = { // TODO: remove any
}; };
if (server.get('env') === 'production') { if (server.get('env') === 'production') {
// trust first proxy // trust first proxy
server.set('trust proxy', 1) server.set('trust proxy', 1);
// serve secure cookies // serve secure cookies
sess.cookie.secure = true sess.cookie.secure = true;
} }
server.use(session(sess)); server.use(session(sess));

View File

@ -1,4 +1,5 @@
import { strictParseInt } from "../parse"; import { strictParseInt } from "../parse";
import { DataConfig } from "./types"; import { DataConfig } from "./types";
const BUILDING_LAYER_DEFINITIONS = { const BUILDING_LAYER_DEFINITIONS = {

View File

@ -1,11 +1,11 @@
import { TileCache } from "./tileCache"; import { getAllLayerNames, getBuildingLayerNames, getBuildingsDataConfig, getHighlightDataConfig } from "./dataDefinition";
import { BoundingBox, TileParams, Tile } from "./types";
import { getBuildingsDataConfig, getHighlightDataConfig, getAllLayerNames, getBuildingLayerNames } from "./dataDefinition";
import { isOutsideExtent } from "./util";
import { renderDataSourceTile } from "./renderers/renderDataSourceTile";
import { getTileWithCaching } from "./renderers/getTileWithCaching";
import { stitchTile } from "./renderers/stitchTile";
import { createBlankTile } from "./renderers/createBlankTile"; import { createBlankTile } from "./renderers/createBlankTile";
import { getTileWithCaching } from "./renderers/getTileWithCaching";
import { renderDataSourceTile } from "./renderers/renderDataSourceTile";
import { stitchTile } from "./renderers/stitchTile";
import { TileCache } from "./tileCache";
import { BoundingBox, Tile, TileParams } from "./types";
import { isOutsideExtent } from "./util";
/** /**
* A list of all tilesets handled by the tile server * A list of all tilesets handled by the tile server

View File

@ -1,5 +1,5 @@
import { TileParams, RendererFunction, Tile } from "../types";
import { TileCache } from "../tileCache"; import { TileCache } from "../tileCache";
import { RendererFunction, Tile, TileParams } from "../types";
async function getTileWithCaching(tileParams: TileParams, dataParams: any, tileCache: TileCache, renderTile: RendererFunction): Promise<Tile> { async function getTileWithCaching(tileParams: TileParams, dataParams: any, tileCache: TileCache, renderTile: RendererFunction): Promise<Tile> {

View File

@ -1,11 +1,10 @@
import path from 'path';
import mapnik from "mapnik"; import mapnik from "mapnik";
import path from 'path';
import { TileParams, Tile, TableDefinitionFunction } from "../types";
import { getBbox, TILE_SIZE } from "../util";
import { promisify } from "util"; import { promisify } from "util";
import { TableDefinitionFunction, Tile, TileParams } from "../types";
import { getBbox, TILE_SIZE } from "../util";
const TILE_BUFFER_SIZE = 64; const TILE_BUFFER_SIZE = 64;
const PROJ4_STRING = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'; const PROJ4_STRING = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over';

View File

@ -1,6 +1,6 @@
import sharp from 'sharp'; import sharp from 'sharp';
import { TileParams, RendererFunction, Tile } from "../types"; import { RendererFunction, Tile, TileParams } from "../types";
import { getBbox, getXYZ, TILE_SIZE } from "../util"; import { getBbox, getXYZ, TILE_SIZE } from "../util";

View File

@ -16,12 +16,12 @@
// Using node-fs package to patch fs // Using node-fs package to patch fs
// for node >10 we could drop this in favour of fs.mkdir (which has recursive option) // for node >10 we could drop this in favour of fs.mkdir (which has recursive option)
// and then use stdlib `import fs from 'fs';` // and then use stdlib `import fs from 'fs';`
import fs from 'node-fs';
import { promisify } from 'util'
import { Image } from 'mapnik'; import { Image } from 'mapnik';
import fs from 'node-fs';
import { promisify } from 'util';
import { TileParams, BoundingBox } from './types'; import { BoundingBox, TileParams } from './types';
import { getXYZ, formatParams } from './util'; import { formatParams, getXYZ } from './util';
// TODO: switch to modern node and use built-in fs with promise-based API // TODO: switch to modern node and use built-in fs with promise-based API
const readFile = promisify(fs.readFile), const readFile = promisify(fs.readFile),
@ -113,7 +113,7 @@ class TileCache {
if(!this.shouldBulkClearTileset(tileset)) continue; if(!this.shouldBulkClearTileset(tileset)) continue;
for (let z = this.cacheDomain.minZoom; z <= this.cacheDomain.maxZoom; z++) { for (let z = this.cacheDomain.minZoom; z <= this.cacheDomain.maxZoom; z++) {
let tileBounds = getXYZ(bbox, z) let tileBounds = getXYZ(bbox, z);
for (let x = tileBounds.minX; x <= tileBounds.maxX; x++) { for (let x = tileBounds.minX; x <= tileBounds.maxX; x++) {
for (let y = tileBounds.minY; y <= tileBounds.maxY; y++) { for (let y = tileBounds.minY; y <= tileBounds.maxY; y++) {
for (const scale of this.cacheDomain.scales) { for (const scale of this.cacheDomain.scales) {

View File

@ -5,10 +5,11 @@
*/ */
import express from 'express'; import express from 'express';
import { strictParseInt } from '../parse';
import { TileParams } from './types';
import { renderTile, allTilesets } from './rendererDefinition';
import asyncController from '../api/routes/asyncController'; import asyncController from '../api/routes/asyncController';
import { strictParseInt } from '../parse';
import { allTilesets, renderTile } from './rendererDefinition';
import { TileParams } from './types';
const handleTileRequest = asyncController(async function (req: express.Request, res: express.Response) { const handleTileRequest = asyncController(async function (req: express.Request, res: express.Response) {
try { try {
@ -30,7 +31,7 @@ const handleTileRequest = asyncController(async function (req: express.Request,
}); });
// tiles router // tiles router
const router = express.Router() const router = express.Router();
router.get('/:tileset/:z/:x/:y(\\d+):scale(@\\dx)?.png', handleTileRequest); router.get('/:tileset/:z/:x/:y(\\d+):scale(@\\dx)?.png', handleTileRequest);

View File

@ -44,7 +44,7 @@ type Tile = Image | Sharp;
type RendererFunction = (tileParams: TileParams, dataParams: any) => Promise<Tile>; type RendererFunction = (tileParams: TileParams, dataParams: any) => Promise<Tile>;
interface TileRenderer { interface TileRenderer {
getTile: RendererFunction getTile: RendererFunction;
} }
export { export {

View File

@ -1,6 +1,6 @@
import SphericalMercator from '@mapbox/sphericalmercator'; import SphericalMercator from '@mapbox/sphericalmercator';
import { TileParams, BoundingBox } from './types'; import { BoundingBox, TileParams } from './types';
const TILE_SIZE = 256; const TILE_SIZE = 256;
@ -13,7 +13,7 @@ function getBbox(z, x, y) {
} }
function getXYZ(bbox, z) { function getXYZ(bbox, z) {
return mercator.xyz(bbox, z, false, '900913') return mercator.xyz(bbox, z, false, '900913');
} }
function formatParams({ tileset, z, x, y, scale }: TileParams): string { function formatParams({ tileset, z, x, y, scale }: TileParams): string {

View File

@ -1,6 +1,19 @@
{ {
"defaultSeverity": "warning", "defaultSeverity": "warning",
"rules": { "rules": {
"eofline": true "eofline": true,
"ordered-imports": [
true,
{
"grouped-imports": true,
"groups": [
{ "name": "css", "match": "\\.css$", "order": 40 },
{ "name": "parent directories", "match": "^\\.\\.", "order": 20 },
{ "name": "current directory", "match": "^\\.", "order": 30 },
{ "name": "libraries", "match": ".*", "order": 10 }
]
}
],
"semicolon": true
} }
} }