Merge pull request #564 from colouring-london/feature/menu-sidebar
Feature: menu sidebar
This commit is contained in:
commit
57a43176d6
@ -1,5 +1,15 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"jest",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:react/recommended"
|
||||
@ -11,7 +21,6 @@
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
@ -32,10 +41,6 @@
|
||||
"no-multiple-empty-lines": ["warn", {"max": 1}],
|
||||
"prefer-const": "warn"
|
||||
},
|
||||
"plugins": [
|
||||
"jest",
|
||||
"react"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
|
3346
app/package-lock.json
generated
3346
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,28 +9,28 @@
|
||||
"start": "razzle start",
|
||||
"build": "razzle build",
|
||||
"test": "razzle test --env=jsdom",
|
||||
"lint": "eslint .",
|
||||
"lint": "eslint --ext .tsx --ext .ts .",
|
||||
"start:prod": "NODE_ENV=production node build/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.21",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.10.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"@mapbox/sphericalmercator": "^1.1.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"bootstrap": "^4.4.1",
|
||||
"connect-pg-simple": "^6.0.1",
|
||||
"connect-pg-simple": "^6.1.0",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"leaflet": "^1.6.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mapnik": "^4.2.1",
|
||||
"mapnik": "^4.4.0",
|
||||
"node-fs": "^0.1.7",
|
||||
"nodemailer": "^6.3.0",
|
||||
"nodemailer": "^6.4.6",
|
||||
"pg-promise": "^8.7.5",
|
||||
"query-string": "^6.8.2",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"query-string": "^6.12.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-leaflet": "^1.0.1",
|
||||
"react-leaflet-universal": "^1.2.0",
|
||||
"react-router-dom": "^5.0.1",
|
||||
@ -39,30 +39,30 @@
|
||||
"use-throttle": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/express-session": "^1.15.16",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/express": "^4.17.5",
|
||||
"@types/express-session": "^1.17.0",
|
||||
"@types/jest": "^24.9.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/lodash.isequal": "^4.5.5",
|
||||
"@types/mapbox__sphericalmercator": "^1.1.3",
|
||||
"@types/node": "^12.12.25",
|
||||
"@types/nodemailer": "^6.2.2",
|
||||
"@types/react": "^16.9.16",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"@types/react-leaflet": "^2.5.0",
|
||||
"@types/node": "^12.12.35",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/react": "^16.9.33",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/react-leaflet": "^2.5.1",
|
||||
"@types/react-router-dom": "^4.3.5",
|
||||
"@types/sharp": "^0.22.3",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-jest": "^22.21.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"razzle": "^3.0.0",
|
||||
"razzle-plugin-typescript": "^3.0.0",
|
||||
"ts-jest": "^24.2.0",
|
||||
"tslint": "^5.20.1",
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.7.3"
|
||||
"ts-jest": "^24.3.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
|
@ -1,5 +1,19 @@
|
||||
module.exports = {
|
||||
plugins: ['typescript'],
|
||||
plugins: [
|
||||
{
|
||||
name: "typescript",
|
||||
options: {
|
||||
useBabel: true,
|
||||
useEslint: true,
|
||||
forkTsChecker: {
|
||||
tsconfig: "./tsconfig.json",
|
||||
tslint: undefined,
|
||||
watch: "./src",
|
||||
typeCheck: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
modify: (config, { target, dev }, webpack) => {
|
||||
// load webfonts
|
||||
rules = config.module.rules || [];
|
||||
|
@ -15,7 +15,7 @@ import * as userService from '../services/user';
|
||||
const getBuildingsByLocation = asyncController(async (req: express.Request, res: express.Response) => {
|
||||
const { lng, lat } = req.query;
|
||||
try {
|
||||
const result = await buildingService.queryBuildingsAtPoint(lng, lat);
|
||||
const result = await buildingService.queryBuildingsAtPoint(Number(lng), Number(lat));
|
||||
res.send(result);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -27,7 +27,7 @@ const getBuildingsByLocation = asyncController(async (req: express.Request, res:
|
||||
const getBuildingsByReference = asyncController(async (req: express.Request, res: express.Response) => {
|
||||
const { key, id } = req.query;
|
||||
try {
|
||||
const result = await buildingService.queryBuildingsByReference(key, id);
|
||||
const result = await buildingService.queryBuildingsByReference(String(key), String(id));
|
||||
res.send(result);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -53,7 +53,7 @@ const updateBuildingById = asyncController(async (req: express.Request, res: exp
|
||||
await updateBuilding(req, res, req.session.user_id);
|
||||
} else if (req.query.api_key) {
|
||||
try {
|
||||
const user = await userService.authAPIUser(req.query.api_key);
|
||||
const user = await userService.authAPIUser(String(req.query.api_key));
|
||||
await updateBuilding(req, res, user.user_id);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
@ -106,7 +106,7 @@ const getBuildingLikeById = asyncController(async (req: express.Request, res: ex
|
||||
}
|
||||
|
||||
const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true);
|
||||
|
||||
|
||||
try {
|
||||
const like = await buildingService.getBuildingLikeById(buildingId, req.session.user_id);
|
||||
|
||||
@ -146,7 +146,7 @@ const updateBuildingLikeById = asyncController(async (req: express.Request, res:
|
||||
if(error instanceof UserError) {
|
||||
throw new ApiUserError(error.message, error);
|
||||
}
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@ import * as leadersService from '../services/leaderboard';
|
||||
|
||||
const getLeaders = asyncController(async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const number_limit = req.query.number_limit;
|
||||
const time_limit = req.query.time_limit;
|
||||
const number_limit = Number(req.query.number_limit);
|
||||
const time_limit = Number(req.query.time_limit);
|
||||
const result = await leadersService.getLeaders(number_limit, time_limit);
|
||||
res.send({
|
||||
leaders: result
|
||||
|
@ -1,35 +1,42 @@
|
||||
import db from '../../db';
|
||||
|
||||
async function getLeaders(number_limit: number, time_limit: number) {
|
||||
// Hard constraint on number of users returned
|
||||
const max_limit = 100;
|
||||
number_limit = Math.min(number_limit, max_limit);
|
||||
|
||||
try {
|
||||
let leaders;
|
||||
if(time_limit > 0){
|
||||
return await db.manyOrNone(
|
||||
`SELECT count(log_id) as number_edits, username
|
||||
FROM logs, users
|
||||
WHERE logs.user_id=users.user_id
|
||||
AND CURRENT_TIMESTAMP::DATE - log_timestamp::DATE <= $1
|
||||
AND NOT (users.username = 'casa_friendly_robot')
|
||||
AND NOT (users.username = 'colouringlondon')
|
||||
GROUP by users.username
|
||||
ORDER BY number_edits DESC
|
||||
LIMIT $2`, [time_limit, number_limit]
|
||||
);
|
||||
|
||||
}else{
|
||||
return await db.manyOrNone(
|
||||
`SELECT count(log_id) as number_edits, username
|
||||
FROM logs, users
|
||||
WHERE logs.user_id=users.user_id
|
||||
AND NOT (users.username = 'casa_friendly_robot')
|
||||
AND NOT (users.username = 'colouringlondon')
|
||||
GROUP by users.username
|
||||
ORDER BY number_edits DESC
|
||||
LIMIT $1`, [number_limit]
|
||||
leaders = await db.manyOrNone(
|
||||
`SELECT count(log_id) as number_edits, username
|
||||
FROM logs, users
|
||||
WHERE logs.user_id = users.user_id
|
||||
AND CURRENT_TIMESTAMP::DATE - log_timestamp::DATE <= $1
|
||||
AND NOT (users.username = 'casa_friendly_robot')
|
||||
AND NOT (users.username = 'colouringlondon')
|
||||
GROUP by users.username
|
||||
ORDER BY number_edits DESC
|
||||
LIMIT $2`, [time_limit, number_limit]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
leaders = await db.manyOrNone(
|
||||
`SELECT count(log_id) as number_edits, username
|
||||
FROM logs, users
|
||||
WHERE logs.user_id = users.user_id
|
||||
AND NOT (users.username = 'casa_friendly_robot')
|
||||
AND NOT (users.username = 'colouringlondon')
|
||||
GROUP by users.username
|
||||
ORDER BY number_edits DESC
|
||||
LIMIT $1`, [number_limit]
|
||||
);
|
||||
}
|
||||
return leaders.map(d => {
|
||||
return {username: d.username, number_edits: Number(d.number_edits)};
|
||||
})
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { Building } from './models/building';
|
||||
import { User } from './models/user';
|
||||
import AboutPage from './pages/about';
|
||||
import ChangesPage from './pages/changes';
|
||||
import CodeOfConductPage from './pages/code-of-conduct';
|
||||
import ContactPage from './pages/contact';
|
||||
import ContributorAgreementPage from './pages/contributor-agreement';
|
||||
import DataAccuracyPage from './pages/data-accuracy';
|
||||
@ -53,7 +54,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
constructor(props: Readonly<AppProps>) {
|
||||
super(props);
|
||||
|
||||
|
||||
this.state = {
|
||||
user: props.user
|
||||
};
|
||||
@ -89,7 +90,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
<Header user={this.state.user} animateLogo={true} />
|
||||
</Route>
|
||||
</Switch>
|
||||
<main>
|
||||
<Switch>
|
||||
<Route exact path="/about.html" component={AboutPage} />
|
||||
<Route exact path="/login.html">
|
||||
@ -114,6 +114,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
<Route exact path="/data-accuracy.html" component={DataAccuracyPage} />
|
||||
<Route exact path="/data-extracts.html" component={DataExtracts} />
|
||||
<Route exact path="/contact.html" component={ContactPage} />
|
||||
<Route exact path="/code-of-conduct.html" component={CodeOfConductPage} />
|
||||
<Route exact path="/leaderboard.html" component={LeaderboardPage} />
|
||||
<Route exact path="/history.html" component={ChangesPage} />
|
||||
<Route exact path={App.mapAppPaths} render={(props) => (
|
||||
@ -127,7 +128,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
)} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</main>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ 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 DynamicsContainer from './data-containers/dynamics';
|
||||
import LocationContainer from './data-containers/location';
|
||||
import PlanningContainer from './data-containers/planning';
|
||||
import SizeContainer from './data-containers/size';
|
||||
@ -44,7 +44,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
|
||||
return <UseContainer
|
||||
{...props}
|
||||
inactive={false}
|
||||
title="Land Use"
|
||||
title="Current Use"
|
||||
intro="How are buildings used, and how does use change over time? Coming soon…"
|
||||
help="https://pages.colouring.london/use"
|
||||
/>;
|
||||
@ -52,7 +52,7 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
|
||||
return <TypeContainer
|
||||
{...props}
|
||||
inactive={false}
|
||||
title="Type"
|
||||
title="Original Use"
|
||||
intro="How were buildings previously used?"
|
||||
help="https://www.pages.colouring.london/buildingtypology"
|
||||
/>;
|
||||
@ -107,7 +107,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
|
||||
title="Community"
|
||||
intro="How does this building work for the local community?"
|
||||
help="https://pages.colouring.london/community"
|
||||
inactive={true}
|
||||
/>;
|
||||
case 'planning':
|
||||
return <PlanningContainer
|
||||
@ -116,12 +115,13 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
|
||||
intro="Planning controls relating to protection and reuse."
|
||||
help="https://pages.colouring.london/planning"
|
||||
/>;
|
||||
case 'like':
|
||||
return <LikeContainer
|
||||
case 'dynamics':
|
||||
return <DynamicsContainer
|
||||
{...props}
|
||||
title="Like Me!"
|
||||
intro="Do you like the building and think it contributes to the city?"
|
||||
help="https://pages.colouring.london/likeme"
|
||||
title="Dynamics"
|
||||
intro="How has the site of this building changed over time?"
|
||||
help="https://pages.colouring.london/buildingcategories"
|
||||
inactive={true}
|
||||
/>;
|
||||
default:
|
||||
return <BuildingNotFound mode="view" />;
|
||||
|
@ -1,25 +1,36 @@
|
||||
/**
|
||||
* Data categories
|
||||
*/
|
||||
.data-category-list {
|
||||
padding: 0 0 0.75rem;
|
||||
text-align: center;
|
||||
.data-category-list {
|
||||
padding: 0px 0 10px 9px;
|
||||
list-style: none;
|
||||
margin: 0 0 0 0.2rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
}
|
||||
.navbar .data-category-list {
|
||||
padding: 0px 0 0px 15px;
|
||||
}
|
||||
.data-category-list li {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
margin: 0.375rem;
|
||||
box-shadow: 0 0 2px 5px #ffffff;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: 2px;
|
||||
box-shadow: 0 0 2px 3px #ffffff;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.navbar .data-category-list li {
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.data-category-list li:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
.data-category-list li:hover {
|
||||
box-shadow: 0 0 2px 5px #00ffff;
|
||||
box-shadow: 0 0 2px 3px #00ffff;
|
||||
z-index: 1;
|
||||
}
|
||||
.data-category-list a {
|
||||
color: #222;
|
||||
@ -41,11 +52,6 @@
|
||||
|
||||
.data-category-list .category {
|
||||
text-align: center;
|
||||
font-size: 1.4em;
|
||||
font-size: 1em;
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
.data-category-list .description {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
<ol className="data-category-list">
|
||||
<Category
|
||||
title="Location"
|
||||
desc="Where's the building?"
|
||||
slug="location"
|
||||
help="https://pages.colouring.london/location"
|
||||
inactive={false}
|
||||
@ -20,8 +19,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Land Use"
|
||||
desc="What's it used for?"
|
||||
title="Current Use"
|
||||
slug="use"
|
||||
help="https://pages.colouring.london/use"
|
||||
inactive={true}
|
||||
@ -29,8 +27,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Type"
|
||||
desc="Building type"
|
||||
title="Original Use"
|
||||
slug="type"
|
||||
help="https://pages.colouring.london/buildingtypology"
|
||||
inactive={false}
|
||||
@ -39,7 +36,6 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
/>
|
||||
<Category
|
||||
title="Age"
|
||||
desc="Age & history"
|
||||
slug="age"
|
||||
help="https://pages.colouring.london/age"
|
||||
inactive={false}
|
||||
@ -48,7 +44,6 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
/>
|
||||
<Category
|
||||
title="Size & Shape"
|
||||
desc="Form & scale"
|
||||
slug="size"
|
||||
help="https://pages.colouring.london/shapeandsize"
|
||||
inactive={false}
|
||||
@ -57,7 +52,6 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
/>
|
||||
<Category
|
||||
title="Construction"
|
||||
desc="Methods & materials"
|
||||
slug="construction"
|
||||
help="https://pages.colouring.london/construction"
|
||||
inactive={false}
|
||||
@ -66,7 +60,6 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
/>
|
||||
<Category
|
||||
title="Streetscape"
|
||||
desc="Environment"
|
||||
slug="streetscape"
|
||||
help="https://pages.colouring.london/greenery"
|
||||
inactive={true}
|
||||
@ -75,34 +68,14 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
/>
|
||||
<Category
|
||||
title="Team"
|
||||
desc="Builder & designer"
|
||||
slug="team"
|
||||
help="https://pages.colouring.london/team"
|
||||
inactive={true}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Sustainability"
|
||||
desc="Performance"
|
||||
slug="sustainability"
|
||||
help="https://pages.colouring.london/sustainability"
|
||||
inactive={false}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Community"
|
||||
desc="Public asset?"
|
||||
slug="community"
|
||||
help="https://pages.colouring.london/community"
|
||||
inactive={false}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Planning"
|
||||
desc="Special controls?"
|
||||
slug="planning"
|
||||
help="https://pages.colouring.london/planning"
|
||||
inactive={true}
|
||||
@ -110,10 +83,25 @@ const Categories: React.FC<CategoriesProps> = (props) => (
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Like Me?"
|
||||
desc="Adds to the city?"
|
||||
slug="like"
|
||||
help="https://pages.colouring.london/likeme"
|
||||
title="Sustainability"
|
||||
slug="sustainability"
|
||||
help="https://pages.colouring.london/sustainability"
|
||||
inactive={false}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Dynamics"
|
||||
slug="dynamics"
|
||||
help="https://pages.colouring.london/dynamics"
|
||||
inactive={true}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
/>
|
||||
<Category
|
||||
title="Community"
|
||||
slug="community"
|
||||
help="https://pages.colouring.london/community"
|
||||
inactive={false}
|
||||
mode={props.mode}
|
||||
building_id={props.building_id}
|
||||
@ -126,7 +114,6 @@ interface CategoryProps {
|
||||
building_id?: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
help: string;
|
||||
inactive: boolean;
|
||||
}
|
||||
@ -147,7 +134,6 @@ const Category: React.FC<CategoryProps> = (props) => {
|
||||
}>
|
||||
<div className="category-title-container">
|
||||
<h3 className="category">{props.title}</h3>
|
||||
<p className="description">{props.desc}</p>
|
||||
</div>
|
||||
</NavLink>
|
||||
</li>
|
||||
|
@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { BackIcon }from '../components/icons';
|
||||
|
||||
interface ContainerHeaderProps {
|
||||
cat?: string;
|
||||
@ -11,9 +8,6 @@ interface ContainerHeaderProps {
|
||||
|
||||
const ContainerHeader: React.FunctionComponent<ContainerHeaderProps> = (props) => (
|
||||
<header className={`section-header view ${props.cat ? props.cat : ''} ${props.cat ? `background-${props.cat}` : ''}`}>
|
||||
<Link className="icon-button back" to={props.backLink}>
|
||||
<BackIcon />
|
||||
</Link>
|
||||
<h2 className="h2">{props.title}</h2>
|
||||
<nav className="icon-buttons">
|
||||
{props.children}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import withCopyEdit from '../data-container';
|
||||
import LikeDataEntry from '../data-components/like-data-entry';
|
||||
|
||||
import { CategoryViewProps } from './category-view-props';
|
||||
|
||||
@ -9,6 +10,12 @@ import { CategoryViewProps } from './category-view-props';
|
||||
*/
|
||||
const CommunityView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
<Fragment>
|
||||
<LikeDataEntry
|
||||
userLike={props.building_like}
|
||||
totalLikes={props.building.likes_total}
|
||||
mode={props.mode}
|
||||
onLike={props.onLike}
|
||||
/>
|
||||
<p className="data-intro">{props.intro}</p>
|
||||
<ul className="data-list">
|
||||
<li>Is this a publicly owned building?</li>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { dataFields } from '../../data_fields';
|
||||
import DataEntry from '../data-components/data-entry';
|
||||
import SelectDataEntry from '../data-components/select-data-entry';
|
||||
import withCopyEdit from '../data-container';
|
||||
|
||||
@ -38,7 +37,7 @@ const ConstructionView: React.FunctionComponent<CategoryViewProps> = (props) =>
|
||||
<SelectDataEntry
|
||||
title={dataFields.construction_core_material.title}
|
||||
slug="construction_core_material"
|
||||
value={props.building.construction_core_material} // check
|
||||
value={props.building.construction_core_material}
|
||||
tooltip={dataFields.construction_core_material.tooltip}
|
||||
options={ConstructionMaterialsOptions}
|
||||
mode={props.mode}
|
||||
@ -47,8 +46,9 @@ const ConstructionView: React.FunctionComponent<CategoryViewProps> = (props) =>
|
||||
/>
|
||||
<SelectDataEntry
|
||||
title={dataFields.construction_secondary_materials.title}
|
||||
disabled={true}
|
||||
slug="construction_secondary_materials"
|
||||
value={props.building.construction_secondary_materials} // check
|
||||
value={props.building.construction_secondary_materials}
|
||||
tooltip={dataFields.construction_secondary_materials.tooltip}
|
||||
options={ConstructionMaterialsOptions}
|
||||
mode={props.mode}
|
||||
@ -58,7 +58,7 @@ const ConstructionView: React.FunctionComponent<CategoryViewProps> = (props) =>
|
||||
<SelectDataEntry
|
||||
title={dataFields.construction_roof_covering.title}
|
||||
slug="construction_roof_covering"
|
||||
value={props.building.construction_roof_covering} // check
|
||||
value={props.building.construction_roof_covering}
|
||||
tooltip={dataFields.construction_roof_covering.tooltip}
|
||||
options={RoofCoveringOptions}
|
||||
mode={props.mode}
|
||||
@ -67,7 +67,7 @@ const ConstructionView: React.FunctionComponent<CategoryViewProps> = (props) =>
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const ConstructionContainer = withCopyEdit(ConstructionView);
|
||||
|
||||
|
24
app/src/frontend/building/data-containers/dynamics.tsx
Normal file
24
app/src/frontend/building/data-containers/dynamics.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import withCopyEdit from '../data-container';
|
||||
|
||||
import { CategoryViewProps } from './category-view-props';
|
||||
|
||||
/**
|
||||
* Dynamics view/edit section
|
||||
*/
|
||||
const DynamicsView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
<Fragment>
|
||||
<p className="data-intro">{props.intro}</p>
|
||||
<ul className="data-list">
|
||||
<li>Under threat of demolition (partial/complete?</li>
|
||||
<li>Demolition permit no. issued </li>
|
||||
<li>Whole building demolitions for current year</li>
|
||||
<li>Whole building demolitions since 2000</li>
|
||||
<li>Pairs of construction and demolition dates for previous buildings built on any part of the site</li>
|
||||
</ul>
|
||||
</Fragment>
|
||||
);
|
||||
const DynamicsContainer = withCopyEdit(DynamicsView);
|
||||
|
||||
export default DynamicsContainer;
|
@ -1,23 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import LikeDataEntry from '../data-components/like-data-entry';
|
||||
import withCopyEdit from '../data-container';
|
||||
|
||||
import { CategoryViewProps } from './category-view-props';
|
||||
|
||||
/**
|
||||
* Like view/edit section
|
||||
*/
|
||||
const LikeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
<Fragment>
|
||||
<LikeDataEntry
|
||||
userLike={props.building_like}
|
||||
totalLikes={props.building.likes_total}
|
||||
mode={props.mode}
|
||||
onLike={props.onLike}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const LikeContainer = withCopyEdit(LikeView);
|
||||
|
||||
export default LikeContainer;
|
@ -1,16 +1,15 @@
|
||||
import { parse } from 'query-string';
|
||||
import React from 'react';
|
||||
import { Link, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
|
||||
import { parseJsonOrDefault } from '../../helpers';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import { BackIcon } from '../components/icons';
|
||||
import InfoBox from '../components/info-box';
|
||||
import { dataFields } from '../data_fields';
|
||||
import { User } from '../models/user';
|
||||
|
||||
import DataEntry from './data-components/data-entry';
|
||||
import Sidebar from './sidebar';
|
||||
import Categories from './categories';
|
||||
|
||||
interface MultiEditProps {
|
||||
user?: User;
|
||||
@ -26,6 +25,7 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
|
||||
// special case for likes
|
||||
return (
|
||||
<Sidebar>
|
||||
<Categories mode={'view'} />
|
||||
<section className='data-section'>
|
||||
<header className={`section-header view ${props.category} background-${props.category}`}>
|
||||
<h2 className="h2">Like me!</h2>
|
||||
@ -54,39 +54,37 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
|
||||
|
||||
return (
|
||||
<Sidebar>
|
||||
<Categories mode={'view'} />
|
||||
<section className='data-section'>
|
||||
<header className={`section-header view ${props.category} background-${props.category}`}>
|
||||
<Link
|
||||
className="icon-button back"
|
||||
to={`/edit/${props.category}`}>
|
||||
<BackIcon />
|
||||
</Link>
|
||||
<h2 className="h2">Copy {props.category} data</h2>
|
||||
</header>
|
||||
<form>
|
||||
<div className="section-body">
|
||||
<form>
|
||||
{
|
||||
error ?
|
||||
<ErrorBox msg={error} /> :
|
||||
<InfoBox msg='Click buildings one at a time to colour using the data below' />
|
||||
}
|
||||
{
|
||||
Object.keys(data).map((key => {
|
||||
const info = dataFields[key] || {};
|
||||
return (
|
||||
<DataEntry
|
||||
title={info.title || `Unknown field (${key})`}
|
||||
slug={key}
|
||||
disabled={true}
|
||||
value={data[key]}
|
||||
/>
|
||||
);
|
||||
}))
|
||||
}
|
||||
</form>
|
||||
<form className='buttons-container'>
|
||||
<Link to={`/view/${props.category}`} className='btn btn-secondary'>Back to view</Link>
|
||||
<Link to={`/edit/${props.category}`} className='btn btn-secondary'>Back to edit</Link>
|
||||
</form>
|
||||
{
|
||||
Object.keys(data).map((key => {
|
||||
const info = dataFields[key] || {};
|
||||
return (
|
||||
<DataEntry
|
||||
title={info.title || `Unknown field (${key})`}
|
||||
slug={key}
|
||||
disabled={true}
|
||||
value={data[key]}
|
||||
/>
|
||||
);
|
||||
}))
|
||||
}
|
||||
</form>
|
||||
<form className='buttons-container'>
|
||||
<Link to={`/view/${props.category}`} className='btn btn-secondary'>Back to view</Link>
|
||||
<Link to={`/edit/${props.category}`} className='btn btn-secondary'>Back to edit</Link>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</Sidebar>
|
||||
);
|
||||
|
@ -2,19 +2,43 @@
|
||||
* Sidebar layout
|
||||
*/
|
||||
.info-container {
|
||||
order: 1;
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 95%;
|
||||
width: calc(100% - 40px);
|
||||
border-right: 1px solid #000;
|
||||
z-index: 1001;
|
||||
transition: transform 0.3s;
|
||||
transform: translateX(0);
|
||||
}
|
||||
.info-container.offscreen {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.info-container-collapse {
|
||||
position: absolute;
|
||||
right: -32px;
|
||||
top: 2rem;
|
||||
padding: 2.5rem 0rem;
|
||||
border-radius: 0 .25rem .25rem 0;
|
||||
}
|
||||
@media (min-width: 990px){
|
||||
.info-container {
|
||||
width: 480px; /* to match .main-header menu width */
|
||||
}
|
||||
.info-container.offscreen {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.info-container-collapse {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.info-container-inner {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding: 0 0 2em;
|
||||
background: #fff;
|
||||
overflow-y: auto;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.info-container {
|
||||
order: 0;
|
||||
height: unset;
|
||||
width: 23rem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,20 +50,19 @@
|
||||
clear: both;
|
||||
text-decoration: none;
|
||||
color: #222;
|
||||
padding: 0.75rem 0.25rem 0.5rem 0;
|
||||
padding: 0.75rem 0.25rem 0.5rem 0.75rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.section-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
.section-header h2,
|
||||
.section-header .icon-buttons {
|
||||
display: inline-block;
|
||||
}
|
||||
.section-header .h2 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
.section-header .icon-buttons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@ -141,16 +164,16 @@
|
||||
/**
|
||||
* Data list sections
|
||||
*/
|
||||
|
||||
.section-body {
|
||||
margin-top: 0.75em;
|
||||
padding: 0 0.75em;
|
||||
}
|
||||
.section-body {
|
||||
margin-top: 0.75em;
|
||||
padding: 0 0.75em 5em 0.75em;
|
||||
min-height: 80vh;
|
||||
}
|
||||
.data-section .h3 {
|
||||
margin: 0;
|
||||
}
|
||||
.data-intro {
|
||||
padding: 0 0.5rem 0 2.25rem;
|
||||
padding: 0 0.5rem 0 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.data-section p {
|
||||
@ -158,7 +181,7 @@
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.data-section ul {
|
||||
padding-left: 3.333rem;
|
||||
padding-left: 1.333rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.data-section li {
|
||||
@ -191,6 +214,10 @@
|
||||
.data-section select {
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
.data-section input[type="checkbox"] {
|
||||
position: static;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.data-list dd {
|
||||
margin: 0 0 0.5rem;
|
||||
line-height: 1.5;
|
||||
|
@ -1,11 +1,29 @@
|
||||
import React from 'react';
|
||||
import React, { useState, Fragment } from 'react';
|
||||
|
||||
import './sidebar.css';
|
||||
import { BackIcon, ForwardIcon } from '../components/icons';
|
||||
|
||||
const Sidebar: React.FC<{}> = (props) => (
|
||||
<div id="sidebar" className="info-container">
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
const Sidebar: React.FC<{}> = (props) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div id="sidebar" className={"info-container " + (collapsed? "offscreen": "")}>
|
||||
<button className="info-container-collapse btn btn-light"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
>
|
||||
{
|
||||
collapsed?
|
||||
<ForwardIcon />
|
||||
: <BackIcon />
|
||||
}
|
||||
</button>
|
||||
<div className="info-container-inner">
|
||||
{ props.children }
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Mini-library of icons
|
||||
*/
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faAngleLeft, faCaretDown, faCaretRight, faCaretUp, faCheck, faCheckDouble,
|
||||
import { faAngleLeft, faAngleRight, faCaretDown, faCaretRight, faCaretUp, faCheck, faCheckDouble,
|
||||
faEye, faInfoCircle, faPaintBrush, faQuestionCircle, faSearch, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
@ -15,6 +15,7 @@ library.add(
|
||||
faCheck,
|
||||
faCheckDouble,
|
||||
faAngleLeft,
|
||||
faAngleRight,
|
||||
faCaretDown,
|
||||
faCaretUp,
|
||||
faCaretRight,
|
||||
@ -54,6 +55,10 @@ const BackIcon = () => (
|
||||
<FontAwesomeIcon icon="angle-left" />
|
||||
);
|
||||
|
||||
const ForwardIcon = () => (
|
||||
<FontAwesomeIcon icon="angle-right" />
|
||||
);
|
||||
|
||||
const DownIcon = () => (
|
||||
<FontAwesomeIcon icon="caret-down" />
|
||||
);
|
||||
@ -79,6 +84,7 @@ export {
|
||||
SaveIcon,
|
||||
SaveDoneIcon,
|
||||
BackIcon,
|
||||
ForwardIcon,
|
||||
DownIcon,
|
||||
UpIcon,
|
||||
RightIcon,
|
||||
|
@ -40,9 +40,9 @@ const LogoGrid: React.FunctionComponent = () => (
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="cell background-sustainability"></div>
|
||||
<div className="cell background-community"></div>
|
||||
<div className="cell background-planning"></div>
|
||||
<div className="cell background-like"></div>
|
||||
<div className="cell background-dynamics"></div>
|
||||
<div className="cell background-community"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,27 +2,68 @@
|
||||
* Main header
|
||||
*/
|
||||
.main-header {
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 76px;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid #222;
|
||||
}
|
||||
.main-header .navbar {
|
||||
padding: 0.5em 0.5em 0.5em;
|
||||
}
|
||||
.main-header .navbar-brand {
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
|
||||
.main-header .shorten-username {
|
||||
text-overflow: '…)';
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
max-width: 70vw;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.main-header .shorten-username {
|
||||
max-width: 5vw;
|
||||
@media (min-width: 990px){
|
||||
.main-header {
|
||||
width: 480px; /* to match .info-container menu width */
|
||||
}
|
||||
}
|
||||
}
|
||||
.main-header.navbar {
|
||||
padding: 0;
|
||||
}
|
||||
.nav-header {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #000;
|
||||
}
|
||||
.nav-header a {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.navbar .navbar-toggler {
|
||||
padding: .5rem .75rem .5rem .75rem;
|
||||
}
|
||||
.navbar .navbar-toggler-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
.navbar .close {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 1em;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.main-header .navbar-collapse {
|
||||
height: calc(100vh - 76px);
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
transition: transform 0.3s;
|
||||
transform: translateY(0);
|
||||
background: #fff;
|
||||
border-right: 1px solid #000;
|
||||
}
|
||||
.main-header .navbar-collapse > ul:last-child {
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
.navbar-collapse.collapse {
|
||||
transform: translateY(-100vh);
|
||||
}
|
||||
.navbar .nav-link {
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
.navbar hr {
|
||||
height: 2px;
|
||||
width: auto;
|
||||
margin: 1em;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import './header.css';
|
||||
|
||||
import { Logo } from './components/logo';
|
||||
import { User } from './models/user';
|
||||
import Categories from './building/categories';
|
||||
|
||||
|
||||
interface HeaderProps {
|
||||
@ -41,90 +42,133 @@ class Header extends React.Component<HeaderProps, HeaderState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<header className="main-header">
|
||||
<nav className="navbar navbar-light navbar-expand-lg">
|
||||
<span className="navbar-brand align-self-start">
|
||||
<NavLink to="/">
|
||||
<Logo variant={this.props.animateLogo ? 'animated' : 'default'}/>
|
||||
</NavLink>
|
||||
</span>
|
||||
<button className="navbar-toggler navbar-toggler-right" type="button"
|
||||
<header className="main-header navbar navbar-light">
|
||||
<div className="nav-header">
|
||||
<NavLink to="/">
|
||||
<Logo variant={this.props.animateLogo ? 'animated' : 'default'}/>
|
||||
</NavLink>
|
||||
<button className="navbar-toggler" type="button"
|
||||
onClick={this.handleClick} aria-expanded={!this.state.collapseMenu} aria-label="Toggle navigation">
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
Menu
|
||||
{
|
||||
this.state.collapseMenu ?
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
: <span className="close">×</span>
|
||||
}
|
||||
</button>
|
||||
<div className={this.state.collapseMenu ? 'collapse navbar-collapse' : 'navbar-collapse'}>
|
||||
<ul className="navbar-nav ml-auto">
|
||||
<li className="nav-item">
|
||||
<NavLink to="/view/categories" className="nav-link" onClick={this.handleNavigate}>
|
||||
View/Edit Maps
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london">
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/buildingcategories">
|
||||
Data Categories
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/whoisinvolved">
|
||||
Who’s Involved?
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/data-ethics">
|
||||
Data Ethics
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://discuss.colouring.london">
|
||||
Discuss
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/data-extracts.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Downloads
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/leaderboard.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Leaderboard
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/contact.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Contact
|
||||
</NavLink>
|
||||
</li>
|
||||
{
|
||||
this.props.user?
|
||||
(
|
||||
<li className="nav-item">
|
||||
<NavLink to="/my-account.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Account <span className="shorten-username">({this.props.user.username})</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
):
|
||||
(
|
||||
<Fragment>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/login.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Log in
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/sign-up.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Sign up
|
||||
</NavLink>
|
||||
</li>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<nav className={this.state.collapseMenu ? 'collapse navbar-collapse' : 'navbar-collapse'}>
|
||||
<Categories mode='view' />
|
||||
<hr />
|
||||
<ul className="navbar-nav flex-column">
|
||||
<li className="nav-item">
|
||||
<NavLink to="/view/categories" className="nav-link" onClick={this.handleNavigate}>
|
||||
View/Edit Maps
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/data-extracts.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Downloads
|
||||
</NavLink>
|
||||
</li>
|
||||
{/*
|
||||
<li className="nav-item">
|
||||
<NavLink to="/showcase.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Showcase
|
||||
</NavLink>
|
||||
</li>
|
||||
*/}
|
||||
<li className="nav-item">
|
||||
<NavLink to="/leaderboard.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Leaderboard
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://discuss.colouring.london">
|
||||
Discuss
|
||||
</a>
|
||||
</li>
|
||||
{
|
||||
this.props.user?
|
||||
(
|
||||
<li className="nav-item">
|
||||
<NavLink to="/my-account.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Account <span className="shorten-username">({this.props.user.username})</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
):
|
||||
(
|
||||
<Fragment>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/login.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Log in
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/sign-up.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Sign up
|
||||
</NavLink>
|
||||
</li>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<hr />
|
||||
<ul className="navbar-nav flex-column">
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london">
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/buildingcategories">
|
||||
Data Categories
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/whoisinvolved">
|
||||
Who’s Involved?
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" href="https://pages.colouring.london/data-ethics">
|
||||
Data Ethics
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<ul className="navbar-nav flex-column">
|
||||
<li className="nav-item">
|
||||
<NavLink to="/contact.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Contact
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/privacy-policy.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Privacy Policy
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/contributor-agreement.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Contributor Agreement
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/code-of-conduct.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Code of Conduct
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/data-accuracy.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Data Accuracy Agreement
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/ordnance-survey-uprn.html" className="nav-link" onClick={this.handleNavigate}>
|
||||
Ordnance Survey terms of UPRN usage
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
@ -66,7 +66,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
async fetchLatestRevision() {
|
||||
try {
|
||||
const {latestRevisionId} = await apiGet(`/api/buildings/revision`);
|
||||
|
||||
|
||||
this.increaseRevision(latestRevisionId);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
@ -74,7 +74,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches building data if a building is selected but no data provided through
|
||||
* Fetches building data if a building is selected but no data provided through
|
||||
* props (from server-side rendering)
|
||||
*/
|
||||
async fetchBuildingData() {
|
||||
@ -173,13 +173,13 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
*/
|
||||
colourBuilding(building: Building) {
|
||||
const cat = this.props.match.params.category;
|
||||
|
||||
|
||||
if (cat === 'like') {
|
||||
this.likeBuilding(building.building_id);
|
||||
} else {
|
||||
const data = parseJsonOrDefault(this.getMultiEditDataString());
|
||||
|
||||
|
||||
|
||||
if (data != undefined && !Object.values(data).some(x => x == undefined)) {
|
||||
this.updateBuilding(building.building_id, data);
|
||||
}
|
||||
@ -224,7 +224,9 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
<Fragment>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Welcome />
|
||||
<Sidebar>
|
||||
<Welcome />
|
||||
</Sidebar>
|
||||
</Route>
|
||||
<Route exact path="/:mode/categories/:building?">
|
||||
<Sidebar>
|
||||
@ -240,6 +242,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
)} />
|
||||
<Route exact path="/:mode/:cat/:building?">
|
||||
<Sidebar>
|
||||
<Categories mode={mode || 'view'} building_id={building_id} />
|
||||
<BuildingView
|
||||
mode={viewEditMode}
|
||||
cat={category}
|
||||
@ -252,11 +255,12 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
</Route>
|
||||
<Route exact path="/:mode/:cat/:building/history">
|
||||
<Sidebar>
|
||||
<Categories mode={mode || 'view'} building_id={building_id} />
|
||||
<EditHistory building={this.state.building} />
|
||||
</Sidebar>
|
||||
</Route>
|
||||
<Route exact path="/:mode(view|edit|multi-edit)"
|
||||
render={props => (<Redirect to={`/${props.match.params.mode}/categories`} />)}
|
||||
render={props => (<Redirect to={`/${props.match.params.mode}/categories`} />)}
|
||||
/>
|
||||
</Switch>
|
||||
<ColouringMap
|
||||
|
@ -10,7 +10,7 @@
|
||||
min-width: 14rem;
|
||||
max-width: 14rem;
|
||||
max-height: 60%;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
.map-legend .logo {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: 990px) {
|
||||
.map-legend .logo {
|
||||
display: block;
|
||||
}
|
||||
@ -45,7 +45,7 @@
|
||||
bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: 990px) {
|
||||
.map-legend {
|
||||
bottom: 2.5rem;
|
||||
}
|
||||
@ -94,7 +94,7 @@
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 0.5rem;
|
||||
|
||||
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
line-height: 0.5;
|
||||
@ -121,4 +121,4 @@
|
||||
.map-legend .logo .cell {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
@ -118,11 +118,11 @@ const LEGEND_CONFIG = {
|
||||
{ color: '#858ed4', text: 'Locally listed'},
|
||||
]
|
||||
},
|
||||
community: {
|
||||
title: 'Community',
|
||||
dynamics: {
|
||||
title: 'Dynamics',
|
||||
elements: []
|
||||
},
|
||||
like: {
|
||||
community: {
|
||||
title: 'Like Me',
|
||||
elements: [
|
||||
{ color: '#bd0026', text: '👍👍👍👍 100+' },
|
||||
@ -179,7 +179,6 @@ class Legend extends React.Component<LegendProps, LegendState> {
|
||||
this.setState({collapseList: (e.target.outerHeight < 670 || e.target.outerWidth < 768)}); // magic number needs to be consistent with CSS expander-button media query
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const details = LEGEND_CONFIG[this.props.slug] || {};
|
||||
const title = details.title || "";
|
||||
@ -187,7 +186,7 @@ class Legend extends React.Component<LegendProps, LegendState> {
|
||||
|
||||
return (
|
||||
<div className="map-legend">
|
||||
<Logo variant='gray' />
|
||||
<Logo variant="default" />
|
||||
<h4 className="h4">
|
||||
{ title }
|
||||
</h4>
|
||||
|
@ -1,17 +1,14 @@
|
||||
.map-notice {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
box-shadow: 0px 0px 1px 1px #222;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
@media (min-width: 990px) {
|
||||
.map-container {
|
||||
left: 480px;
|
||||
}
|
||||
}
|
||||
.leaflet-container {
|
||||
height: 100%;
|
||||
@ -21,21 +18,29 @@
|
||||
border: 1px solid #fff;
|
||||
box-shadow: 0 0 1px 1px #222;
|
||||
}
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.leaflet-grab {
|
||||
cursor: crosshair;
|
||||
}
|
||||
@media (min-width: 768px){
|
||||
.map-notice {
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
left: 0.5rem;
|
||||
z-index: 1000;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 1px 1px #222;
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 990px){
|
||||
/* Only show the "Click a building ..." notice for larger screens */
|
||||
.map-notice {
|
||||
left: 0.5rem;
|
||||
top: 3.5rem;
|
||||
visibility: visible;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
||||
|
||||
|
||||
const boundaryStyleFn = () => ({color: '#bbb', fill: false});
|
||||
const boundaryLayer = this.state.boundary &&
|
||||
const boundaryLayer = this.state.boundary &&
|
||||
<GeoJSON data={this.state.boundary} style={boundaryStyleFn}/>;
|
||||
|
||||
// colour-data tiles
|
||||
@ -133,7 +133,7 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
||||
size: 'size_height',
|
||||
construction: 'construction_core_material',
|
||||
location: 'location',
|
||||
like: 'likes',
|
||||
community: 'likes',
|
||||
planning: 'planning_combined',
|
||||
sustainability: 'sust_dec',
|
||||
type: 'building_attachment_form',
|
||||
|
@ -42,7 +42,7 @@
|
||||
padding: 0.25rem 0.2rem;
|
||||
margin-right: 0.1rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: 990px) {
|
||||
/* The following is a fix (?) for the truncation of the "Search for postcode" text */
|
||||
.form-inline .form-control {
|
||||
width: 200px;
|
||||
|
@ -21,9 +21,8 @@
|
||||
background-image: none;
|
||||
border-color: #343a40;
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
@media (max-width: 990px){
|
||||
.theme-switcher {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ const ChangesPage = (props: RouteComponentProps) => {
|
||||
if(after_id) {
|
||||
url = `${url}&after_id=${after_id}`;
|
||||
}
|
||||
|
||||
|
||||
if (before_id) {
|
||||
url = `${url}&before_id=${before_id}`;
|
||||
}
|
||||
@ -47,14 +47,15 @@ const ChangesPage = (props: RouteComponentProps) => {
|
||||
setPaging(paging);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Connection problem. Please try again later...');
|
||||
console.error('Connection problem. Please try again later...');
|
||||
setError(err);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [props.location.search]);
|
||||
|
||||
|
||||
return (
|
||||
<article>
|
||||
<section className="main-col">
|
||||
@ -81,7 +82,7 @@ const ChangesPage = (props: RouteComponentProps) => {
|
||||
(history?.length === 0) &&
|
||||
<InfoBox msg="No changes so far"></InfoBox>
|
||||
}
|
||||
{
|
||||
{
|
||||
(history != undefined && history.length > 0) &&
|
||||
history.map(entry => (
|
||||
<li key={`${entry.revision_id}`} className="edit-history-list-element">
|
||||
|
107
app/src/frontend/pages/code-of-conduct.tsx
Normal file
107
app/src/frontend/pages/code-of-conduct.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import InfoBox from '../components/info-box';
|
||||
|
||||
|
||||
const CodeOfConductPage = () => (
|
||||
<article>
|
||||
<section className="main-col">
|
||||
<h1 className="h2">Code of Conduct</h1>
|
||||
|
||||
<InfoBox msg="Draft code of conduct for discussion" />
|
||||
|
||||
<h2 className="h3">Our Pledge</h2>
|
||||
|
||||
<p>
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
</p>
|
||||
<h2 className="h3">Our Standards</h2>
|
||||
<p>
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Using welcoming and inclusive language</li>
|
||||
<li>Being respectful of differing viewpoints and experiences</li>
|
||||
<li>Gracefully accepting constructive criticism</li>
|
||||
<li>Focusing on what is best for the community</li>
|
||||
<li>Showing empathy towards other community members</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Examples of unacceptable behavior by participants include:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>The use of sexualized language or imagery and unwelcome sexual attention or</li>
|
||||
advances
|
||||
<li>Trolling, insulting/derogatory comments, and personal or political attacks</li>
|
||||
<li>Public or private harassment</li>
|
||||
<li>Publishing others' private information, such as a physical or electronic address, without explicit permission</li>
|
||||
|
||||
<li>Other conduct which could reasonably be considered inappropriate in a professional setting</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="h3">Our Responsibilities</h2>
|
||||
<p>
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
</p>
|
||||
|
||||
<h2 className="h3">Scope</h2>
|
||||
<p>
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
</p>
|
||||
|
||||
<h2 className="h3">Enforcement</h2>
|
||||
<p>
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting the project team at <a
|
||||
href="mailto:team@colouring.london">team@colouring.london</a>. All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. The project team is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of
|
||||
specific enforcement policies may be posted separately.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
</p>
|
||||
|
||||
<h2 className="h3">Attribution</h2>
|
||||
<p>
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
|
||||
available at <a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct.html">
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For answers to common questions about this code of conduct, see <a
|
||||
href="https://www.contributor-covenant.org/faq">https://www.contributor-covenant.org/faq</a>
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
|
||||
export default CodeOfConductPage;
|
@ -1,34 +0,0 @@
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 60%;
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
table th, td {
|
||||
border: 1px solid black;
|
||||
text-align: left;
|
||||
padding-left: 1%;
|
||||
}
|
||||
|
||||
table tr:nth-child(odd) {
|
||||
background: #f6f8fa;
|
||||
}
|
||||
|
||||
table tr:nth-child(1) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#title {
|
||||
text-align: center;
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
|
||||
#radiogroup {
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin: 0 2px 0 10px;
|
||||
}
|
@ -1,25 +1,23 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import './leaderboard.css';
|
||||
|
||||
interface Leader {
|
||||
number_edits: string;
|
||||
username: string;
|
||||
}
|
||||
interface Leader {
|
||||
number_edits: number;
|
||||
username: string;
|
||||
}
|
||||
|
||||
|
||||
interface LeaderboardProps {
|
||||
}
|
||||
interface LeaderboardProps {}
|
||||
|
||||
|
||||
interface LeaderboardState {
|
||||
leaders: Leader[];
|
||||
fetching: boolean;
|
||||
interface LeaderboardState {
|
||||
leaders: Leader[];
|
||||
fetching: boolean;
|
||||
|
||||
//We need to track the state of the radio buttons to ensure their current state is shown correctly when the view is (re)rendered
|
||||
number_limit: number;
|
||||
time_limit: number;
|
||||
}
|
||||
//We need to track the state of the radio buttons to ensure their current state is shown correctly when the view is (re)rendered
|
||||
number_limit: number;
|
||||
time_limit: number;
|
||||
}
|
||||
|
||||
|
||||
class LeaderboardPage extends Component<LeaderboardProps, LeaderboardState> {
|
||||
@ -27,44 +25,38 @@ class LeaderboardPage extends Component<LeaderboardProps, LeaderboardState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
leaders: [],
|
||||
fetching: false,
|
||||
leaders: [],
|
||||
fetching: false,
|
||||
number_limit: 10,
|
||||
time_limit: -1
|
||||
};
|
||||
this.getLeaders = this.getLeaders.bind(this);
|
||||
this.renderTableData = this.renderTableData.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.getLeaders = this.getLeaders.bind(this);
|
||||
this.renderTableData = this.renderTableData.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
handleChange(e) {
|
||||
if(e.target.name == 'number_limit'){
|
||||
this.getLeaders(e.target.value, this.state.time_limit);
|
||||
this.setState({number_limit: e.target.value});
|
||||
}else {
|
||||
} else {
|
||||
this.getLeaders(this.state.number_limit, e.target.value);
|
||||
this.setState({time_limit: e.target.value});
|
||||
this.setState({time_limit: e.target.value});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
this.getLeaders(this.state.number_limit, this.state.time_limit);
|
||||
|
||||
componentDidMount() {
|
||||
this.getLeaders(this.state.number_limit, this.state.time_limit);
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {}
|
||||
|
||||
|
||||
getLeaders(number_limit, time_limit) {
|
||||
|
||||
getLeaders(number_limit: number, time_limit: number) {
|
||||
this.setState({
|
||||
fetching: true
|
||||
});
|
||||
|
||||
fetch(
|
||||
'/api/leaderboard/leaders?number_limit=' + number_limit + '&time_limit='+time_limit
|
||||
`/api/leaderboard/leaders?number_limit=${number_limit}&time_limit=${time_limit}`
|
||||
).then(
|
||||
(res) => res.json()
|
||||
).then((data) => {
|
||||
@ -73,7 +65,7 @@ class LeaderboardPage extends Component<LeaderboardProps, LeaderboardState> {
|
||||
leaders: data.leaders,
|
||||
fetching: false
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
console.error(data);
|
||||
|
||||
@ -91,59 +83,115 @@ class LeaderboardPage extends Component<LeaderboardProps, LeaderboardState> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
renderTableData() {
|
||||
return this.state.leaders.map((u, i) => {
|
||||
const username = u.username;
|
||||
const username = u.username;
|
||||
const number_edits = u.number_edits;
|
||||
|
||||
return (
|
||||
<tr key={username}>
|
||||
<td>{i+1}</td>
|
||||
<td>{username}</td>
|
||||
<td>{number_edits}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={username}>
|
||||
<th scope="row">{i+1}</th>
|
||||
<td>{username}</td>
|
||||
<td>{number_edits.toLocaleString()}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
<form id="radiogroup">
|
||||
<div id="number-radiogroup" >
|
||||
<p>Select number of users to be displayed: <br/>
|
||||
<input type="radio" name="number_limit" value="10" onChange={this.handleChange} checked={10 == this.state.number_limit} />10
|
||||
<input type="radio" name="number_limit" value="100" onChange={this.handleChange} checked={100 == this.state.number_limit} />100
|
||||
</p>
|
||||
</div>
|
||||
<div id="time-radiogroup" >
|
||||
<p>Select time period: <br/>
|
||||
<input type="radio" name="time_limit" value="-1" onChange={this.handleChange} checked={-1 == this.state.time_limit} /> All time
|
||||
<input type="radio" name="time_limit" value="7" onChange={this.handleChange} checked={7 == this.state.time_limit} /> Last 7 days
|
||||
<input type="radio" name="time_limit" value="30" onChange={this.handleChange} checked={30 == this.state.time_limit} /> Last 30 days
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<h1 id='title'>Leader Board</h1>
|
||||
<table id='leaderboard'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Username</th>
|
||||
<th>Contributions</th>
|
||||
</tr>
|
||||
{this.renderTableData()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<article>
|
||||
<section className="main-col">
|
||||
<h1 className="h2">Leaderboard</h1>
|
||||
<form>
|
||||
<label>Select number of users to be displayed</label>
|
||||
<div className="form-group">
|
||||
<div className="form-check-inline">
|
||||
<input
|
||||
type="radio"
|
||||
name="number_limit"
|
||||
id="number_10"
|
||||
className="form-check-input"
|
||||
value="10"
|
||||
onChange={this.handleChange}
|
||||
checked={10 == this.state.number_limit}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="number_10">10</label>
|
||||
</div>
|
||||
<div className="form-check-inline">
|
||||
<input
|
||||
type="radio"
|
||||
name="number_limit"
|
||||
id="number_100"
|
||||
className="form-check-input"
|
||||
value="100"
|
||||
onChange={this.handleChange}
|
||||
checked={100 == this.state.number_limit}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="number_100">100</label>
|
||||
</div>
|
||||
</div>
|
||||
<label>Select time period</label>
|
||||
<div className="form-group">
|
||||
<div className="form-check-inline">
|
||||
<input
|
||||
type="radio"
|
||||
name="time_limit"
|
||||
id="time_all"
|
||||
className="form-check-input"
|
||||
value="-1"
|
||||
onChange={this.handleChange}
|
||||
checked={-1 == this.state.time_limit}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="time_all">All time</label>
|
||||
</div>
|
||||
<div className="form-check-inline">
|
||||
<input
|
||||
type="radio"
|
||||
name="time_limit"
|
||||
id="time_7"
|
||||
className="form-check-input"
|
||||
value="7"
|
||||
onChange={this.handleChange}
|
||||
checked={7 == this.state.time_limit}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="time_7">Last 7 days</label>
|
||||
</div>
|
||||
<div className="form-check-inline">
|
||||
<input
|
||||
type="radio"
|
||||
name="time_limit"
|
||||
id="time_30"
|
||||
className="form-check-input"
|
||||
value="30"
|
||||
onChange={this.handleChange}
|
||||
checked={30 == this.state.time_limit}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="time_30">Last 30 days</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Contributions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderTableData()}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default LeaderboardPage;
|
||||
|
||||
|
@ -1,36 +1,6 @@
|
||||
/**
|
||||
* Welcome jumbotron
|
||||
*/
|
||||
.welcome-float {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
max-height: 95%;
|
||||
max-height: calc(100%-2em);
|
||||
border-radius: 0;
|
||||
overflow-y: auto;
|
||||
.welcome.section-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.welcome-float.jumbotron {
|
||||
padding: 1em 2.5em 1.5em;
|
||||
background: #fff;
|
||||
background-color: rgba(255,255,255,0.95);
|
||||
}
|
||||
@media (min-width: 768px){
|
||||
.welcome-float {
|
||||
left: 50%;
|
||||
margin-left: -22.5em;
|
||||
width: 45em;
|
||||
top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-float .lead {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.welcome-float .lead a {
|
||||
color: #333;
|
||||
border-bottom-color: #333;
|
||||
}
|
@ -4,23 +4,45 @@ import { Link } from 'react-router-dom';
|
||||
import './welcome.css';
|
||||
|
||||
const Welcome = () => (
|
||||
<div className="jumbotron welcome-float">
|
||||
<h1 className="h1">Welcome to Colouring London</h1>
|
||||
<p className="lead">
|
||||
Colouring London is a knowledge exchange platform set up by University College London to help make the city more sustainable. It provides open statistical data on the characteristics of the city's buildings and on the dynamic behaviour of the stock. We're working to collate, collect, generate, verify over fifty types of data and to visualise many of these datasets.
|
||||
<div className="section-body welcome">
|
||||
<h1 className="h2">Welcome to Colouring London!</h1>
|
||||
<p>
|
||||
|
||||
We collect and provide open data about buildings.
|
||||
|
||||
</p>
|
||||
<p className="lead">
|
||||
Our information comes from many different sources. As we are unable to vouch for data accuracy, we are currently experimenting with a range of features including 'data source', 'edit history', and 'entry verification', to assist you in checking reliability and judging how suitable the data are for your intended use. Your help in checking and adding data is very much appreciated.
|
||||
<p>
|
||||
|
||||
Colouring London is a knowledge exchange platform set up by University College
|
||||
London. It provides open statistical data about the city's buildings and the
|
||||
dynamic behaviour of the stock. We're working to collate, collect, generate,
|
||||
verify over fifty types of data and to visualise many of these datasets.
|
||||
|
||||
</p>
|
||||
<p className="lead">
|
||||
All data we collect are made <Link to="/data-extracts.html">openly available</Link>. We just ask you to credit Colouring London and read our <a href="https://www.pages.colouring.london/data-ethics">data ethics policy</a> when using or sharing our data, maps or <a href="https://github.com/tomalrussell/colouring-london">code</a>.
|
||||
<p>
|
||||
|
||||
Our information comes from many different sources. As we are unable to vouch for
|
||||
data accuracy, we are experimenting with how to present data sources, how data
|
||||
are edited over time, and how to ask for data verification, to help you to check
|
||||
reliability and judge how suitable the data are for your intended use. Your help
|
||||
in checking and adding data is very much appreciated.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
All data we collect are made <Link to="/data-extracts.html">openly
|
||||
available</Link>. We ask you to credit Colouring London and read our <a
|
||||
href="https://www.pages.colouring.london/data-ethics">data ethics policy</a> when
|
||||
using or sharing our data, maps or <a
|
||||
href="https://github.com/colouring-london/colouring-london">code</a>.
|
||||
|
||||
</p>
|
||||
<Link to="/view/categories"
|
||||
className="btn btn-outline-dark btn-lg btn-block">
|
||||
Start Colouring Here!
|
||||
</Link>
|
||||
<p>
|
||||
<img src="images/supporter-logos.png" alt="Colouring London collaborating organisations: The Bartlett UCL, Ordnance Survey, Historic England, Greater London Authority" />
|
||||
<img src="images/supporter-logos.png" alt="Colouring London collaborating organisations: The Bartlett UCL, Ordnance Survey, Historic England, Greater London Authority" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -80,15 +80,15 @@
|
||||
.background-team {
|
||||
background-color: #7cbf39;
|
||||
}
|
||||
.background-sustainability {
|
||||
.background-planning {
|
||||
background-color: #57c28e;
|
||||
}
|
||||
.background-community {
|
||||
.background-sustainability {
|
||||
background-color: #6bb1e3;
|
||||
}
|
||||
.background-planning {
|
||||
.background-dynamics {
|
||||
background-color: #aaaaaa;
|
||||
}
|
||||
.background-like {
|
||||
.background-community {
|
||||
background-color: #a3916f;
|
||||
}
|
||||
|
@ -55,11 +55,11 @@ p a.btn:active {
|
||||
h1, h2, h3, h4 {
|
||||
font-weight: normal;
|
||||
}
|
||||
main .h1 {
|
||||
article .h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
main .h2 {
|
||||
article .h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.25em 0 0.5em;
|
||||
}
|
||||
|
@ -1,38 +1,31 @@
|
||||
/**
|
||||
* Main Layout
|
||||
*/
|
||||
|
||||
html, body, #root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media(min-width: 768px) {
|
||||
main {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Text pages
|
||||
*/
|
||||
article {
|
||||
position: absolute;
|
||||
top: 76px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 0 1rem;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@media (min-width: 990px) {
|
||||
article {
|
||||
top: 0px;
|
||||
left: 480px;
|
||||
}
|
||||
}
|
||||
article section {
|
||||
overflow: hidden;
|
||||
margin: 2.25em 0 4em;
|
||||
|
@ -8,7 +8,6 @@ body {
|
||||
}
|
||||
.h2 {
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
}
|
||||
.h3, .h4, .h5 {
|
||||
font-weight: normal;
|
||||
|
@ -57,13 +57,13 @@ const BUILDING_LAYER_DEFINITIONS = {
|
||||
) as size_height`,
|
||||
construction_core_material: `(
|
||||
SELECT
|
||||
b.core_materials,
|
||||
b.construction_core_material::text as construction_core_material,
|
||||
g.geometry_geom
|
||||
FROM
|
||||
geometries as g,
|
||||
buildings as b
|
||||
WHERE g.geometry_id = b.geometry_id
|
||||
) as core_materials`,
|
||||
) as construction_core_material`,
|
||||
location: `(
|
||||
SELECT
|
||||
(
|
||||
@ -116,7 +116,7 @@ const BUILDING_LAYER_DEFINITIONS = {
|
||||
b.planning_in_conservation_area
|
||||
OR b.planning_in_local_list
|
||||
OR b.planning_list_cat is not null
|
||||
) as planning_combined`,
|
||||
) as planning_combined`,
|
||||
conservation_area: `(
|
||||
SELECT
|
||||
g.geometry_geom
|
||||
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"defaultSeverity": "warning",
|
||||
"rules": {
|
||||
"eofline": true,
|
||||
"ordered-imports": [
|
||||
true,
|
||||
{
|
||||
"grouped-imports": true,
|
||||
"groups": [
|
||||
{ "name": "css", "match": "\\.css$", "order": 15 },
|
||||
{ "name": "parent directories", "match": "^\\.\\.", "order": 20 },
|
||||
{ "name": "current directory", "match": "^\\.", "order": 30 },
|
||||
{ "name": "libraries", "match": ".*", "order": 10 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"semicolon": true
|
||||
}
|
||||
}
|
12
migrations/017.construction-materials.down.sql
Normal file
12
migrations/017.construction-materials.down.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- Primary material (brick/clay tile, slate, steel/metal, timber, stone, glass, concrete,
|
||||
-- natural-green or thatch, asphalt or other)
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS construction_core_material;
|
||||
|
||||
--Secondary material
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS construction_secondary_materials;
|
||||
|
||||
--Primary roof material
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS construction_roof_covering;
|
||||
|
||||
DROP TYPE IF EXISTS construction_materials;
|
||||
DROP TYPE IF EXISTS roof_covering;
|
42
migrations/017.construction-materials.up.sql
Normal file
42
migrations/017.construction-materials.up.sql
Normal file
@ -0,0 +1,42 @@
|
||||
-- Launch of Constuction category
|
||||
-- Fields: Core construction material, Secondary construction materials, Roof covering materials
|
||||
|
||||
-- Construction materials: Wood, Stone, Brick, Steel, Reinforced Concrete, Other Metal
|
||||
-- Other Natural Material, Other Man-Made Material
|
||||
CREATE TYPE construction_materials
|
||||
AS ENUM (
|
||||
'Wood',
|
||||
'Stone',
|
||||
'Brick',
|
||||
'Steel',
|
||||
'Reinforced Concrete',
|
||||
'Other Metal',
|
||||
'Other Natural Material',
|
||||
'Other Man-Made Material'
|
||||
);
|
||||
|
||||
-- Roof covering materials: Slate, Clay Tile, Wood, Asphalt, Iron or Steel, Other Metal
|
||||
-- Other Natural Material, Other Man-Made Material
|
||||
CREATE TYPE roof_covering
|
||||
AS ENUM (
|
||||
'Slate',
|
||||
'Clay Tile',
|
||||
'Wood',
|
||||
'Asphalt',
|
||||
'Iron or Steel',
|
||||
'Other Metal',
|
||||
'Other Natural Material',
|
||||
'Other Man-Made Material'
|
||||
);
|
||||
|
||||
-- Core Construction Material
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS construction_core_material construction_materials;
|
||||
|
||||
-- Secondary Construction Materials
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS construction_secondary_materials construction_materials;
|
||||
|
||||
-- Main Roof Covering
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS construction_roof_covering roof_covering;
|
@ -1,8 +0,0 @@
|
||||
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS core_materials;
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS secondary_materials;
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS roof_covering;
|
||||
|
||||
|
||||
DROP TYPE IF EXISTS construction_materials;
|
||||
DROP TYPE IF EXISTS roof_covering;
|
@ -1,38 +0,0 @@
|
||||
-- Launch of Constuction category
|
||||
-- Fields: Core construction material, Secondary construction materials, Roof covering materials
|
||||
|
||||
-- Construction materials: Wood, Stone, Brick, Steel, Reinforced Concrete, Other Metal
|
||||
-- Other Natural Material, Other Man-Made Material
|
||||
CREATE TYPE construction_materials
|
||||
AS ENUM ('Wood',
|
||||
'Stone',
|
||||
'Brick',
|
||||
'Steel',
|
||||
'Reinforced Concrete',
|
||||
'Other Metal',
|
||||
'Other Natural Material',
|
||||
'Other Man-Made Material');
|
||||
|
||||
-- Roof covering materials: Slate, Clay Tile, Wood, Asphalt, Iron or Steel, Other Metal
|
||||
-- Other Natural Material, Other Man-Made Material
|
||||
CREATE TYPE roof_covering
|
||||
AS ENUM ('Slate',
|
||||
'Clay Tile',
|
||||
'Wood',
|
||||
'Asphalt',
|
||||
'Iron or Steel',
|
||||
'Other Metal',
|
||||
'Other Natural Material',
|
||||
'Other Man-Made Material');
|
||||
|
||||
-- Core Construction Material
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS core_materials construction_materials;
|
||||
|
||||
-- Secondary Construction Materials
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS secondary_materials construction_materials;
|
||||
|
||||
-- Main Roof Covering
|
||||
ALTER TABLE buildings
|
||||
ADD COLUMN IF NOT EXISTS roof_covering roof_covering;
|
@ -1,23 +0,0 @@
|
||||
--NOTE Some construction category fields (insulation and glazing) are migrated in sustainability.up.1-extra see #405
|
||||
--Primary material (brick/clay tile, slate, steel/metal, timber, stone, glass, concrete, natural-green or thatch, asphalt or other)
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_primary_mat;
|
||||
|
||||
--Secondary material
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_secondary_mat;
|
||||
|
||||
--Primary roof material
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_primary_roof_mat;
|
||||
|
||||
--Secondary roof material
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_secondary_roof_mat;
|
||||
|
||||
--Has building been extended y/n
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_extension;
|
||||
|
||||
--What kind of extensions (side, rear, loft, basement)
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_extension_type;
|
||||
|
||||
--Glazing type, what is most common glazing type. (unless I add into sustainability for now)
|
||||
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_glazing_type;
|
||||
|
||||
--Also pick up roof in this for
|
Loading…
Reference in New Issue
Block a user