Merge branch 'master' into feature/500-age-source-update
This commit is contained in:
commit
2428b28eda
@ -6,7 +6,7 @@ async function getGlobalEditHistory() {
|
||||
`SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp), username, building_id
|
||||
FROM logs, users
|
||||
WHERE logs.user_id = users.user_id
|
||||
AND log_timestamp >= now() - interval '21 days'
|
||||
AND log_timestamp >= now() - interval '7 days'
|
||||
ORDER BY log_timestamp DESC`
|
||||
);
|
||||
} catch (error) {
|
||||
|
@ -60,7 +60,10 @@ async function authUser(username: string, password: string) {
|
||||
user_id,
|
||||
(
|
||||
pass = crypt($2, pass)
|
||||
) AS auth_ok
|
||||
) AS auth_ok,
|
||||
is_blocked,
|
||||
blocked_on,
|
||||
blocked_reason
|
||||
FROM users
|
||||
WHERE
|
||||
username = $1
|
||||
@ -71,6 +74,9 @@ async function authUser(username: string, password: string) {
|
||||
);
|
||||
|
||||
if (user && user.auth_ok) {
|
||||
if (user.is_blocked) {
|
||||
return { error: `Account temporarily blocked.${user.blocked_reason == undefined ? '' : ' Reason: '+user.blocked_reason}` };
|
||||
}
|
||||
return { user_id: user.user_id };
|
||||
} else {
|
||||
return { error: 'Username or password not recognised' };
|
||||
|
@ -14,7 +14,7 @@ const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (prop
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<div className="form-check">
|
||||
|
@ -28,7 +28,7 @@ const DataEntry: React.FunctionComponent<DataEntryProps> = (props) => {
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined || props.value == ''}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<input className="form-control" type="text"
|
||||
|
@ -57,7 +57,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps> {
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined || props.value.length === 0}
|
||||
/>
|
||||
{
|
||||
(props.mode === 'view')?
|
||||
|
@ -19,7 +19,7 @@ const NumericDataEntry: React.FunctionComponent<NumericDataEntryProps> = (props)
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<input
|
||||
@ -27,10 +27,10 @@ const NumericDataEntry: React.FunctionComponent<NumericDataEntryProps> = (props)
|
||||
type="number"
|
||||
id={props.slug}
|
||||
name={props.slug}
|
||||
value={props.value || ''}
|
||||
step={props.step || 1}
|
||||
value={props.value == undefined ? '' : props.value}
|
||||
step={props.step == undefined ? 1 : props.step}
|
||||
max={props.max}
|
||||
min={props.min || 0}
|
||||
min={props.min}
|
||||
disabled={props.mode === 'view' || props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
onChange={e =>
|
||||
|
@ -17,7 +17,7 @@ const SelectDataEntry: React.FunctionComponent<SelectDataEntryProps> = (props) =
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<select className="form-control"
|
||||
|
@ -16,7 +16,7 @@ const TextboxDataEntry: React.FunctionComponent<TextboxDataEntryProps> = (props)
|
||||
slug={props.slug}
|
||||
title={props.title}
|
||||
tooltip={props.tooltip}
|
||||
disabled={props.disabled}
|
||||
disabled={props.disabled || props.value == undefined}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<textarea
|
||||
|
@ -30,6 +30,8 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
|
||||
// TODO handle changes internally, reporting out date_year, date_upper, date_lower
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
return (
|
||||
<Fragment>
|
||||
<NumericDataEntry
|
||||
@ -39,6 +41,8 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
min={1}
|
||||
max={currentYear}
|
||||
// "type": "year_estimator"
|
||||
/>
|
||||
<NumericDataEntry
|
||||
@ -49,6 +53,8 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={1}
|
||||
max={currentYear}
|
||||
tooltip={dataFields.date_upper.tooltip}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
@ -59,6 +65,8 @@ class YearDataEntry extends Component<YearDataEntryProps, any> {
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={1}
|
||||
max={currentYear}
|
||||
tooltip={dataFields.date_lower.tooltip}
|
||||
/>
|
||||
</Fragment>
|
||||
|
@ -13,73 +13,79 @@ import { CategoryViewProps } from './category-view-props';
|
||||
/**
|
||||
* Age view/edit section
|
||||
*/
|
||||
const AgeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
<Fragment>
|
||||
<YearDataEntry
|
||||
year={props.building.date_year}
|
||||
upper={props.building.date_upper}
|
||||
lower={props.building.date_lower}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.facade_year.title}
|
||||
slug="facade_year"
|
||||
value={props.building.facade_year}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
tooltip={dataFields.facade_year.tooltip}
|
||||
/>
|
||||
<SelectDataEntry
|
||||
title={dataFields.date_source.title}
|
||||
slug="date_source"
|
||||
value={props.building.date_source}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_source.tooltip}
|
||||
placeholder=""
|
||||
options={[
|
||||
"Expert knowledge of building",
|
||||
"Expert estimate from image",
|
||||
"Survey of London",
|
||||
"Pevsner Guides",
|
||||
"Victoria County History",
|
||||
"Local history publication",
|
||||
"Other publication",
|
||||
"National Heritage List for England",
|
||||
"Other database or gazetteer",
|
||||
"Historical map",
|
||||
"Other archive document",
|
||||
"Film/Video",
|
||||
"Other website",
|
||||
"Other"
|
||||
]}
|
||||
/>
|
||||
<TextboxDataEntry
|
||||
title={dataFields.date_source_detail.title}
|
||||
slug="date_source_detail"
|
||||
value={props.building.date_source_detail}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_source_detail.tooltip}
|
||||
/>
|
||||
<MultiDataEntry
|
||||
title={dataFields.date_link.title}
|
||||
slug="date_link"
|
||||
value={props.building.date_link}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_link.tooltip}
|
||||
placeholder="https://..."
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const AgeView: React.FunctionComponent<CategoryViewProps> = (props) => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<YearDataEntry
|
||||
year={props.building.date_year}
|
||||
upper={props.building.date_upper}
|
||||
lower={props.building.date_lower}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.facade_year.title}
|
||||
slug="facade_year"
|
||||
value={props.building.facade_year}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={1}
|
||||
max={currentYear}
|
||||
tooltip={dataFields.facade_year.tooltip}
|
||||
/>
|
||||
<SelectDataEntry
|
||||
title={dataFields.date_source.title}
|
||||
slug="date_source"
|
||||
value={props.building.date_source}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_source.tooltip}
|
||||
placeholder=""
|
||||
options={[
|
||||
"Expert knowledge of building",
|
||||
"Expert estimate from image",
|
||||
"Survey of London",
|
||||
"Pevsner Guides",
|
||||
"Victoria County History",
|
||||
"Local history publication",
|
||||
"Other publication",
|
||||
"National Heritage List for England",
|
||||
"Other database or gazetteer",
|
||||
"Historical map",
|
||||
"Other archive document",
|
||||
"Film/Video",
|
||||
"Other website",
|
||||
"Other"
|
||||
]}
|
||||
/>
|
||||
<TextboxDataEntry
|
||||
title={dataFields.date_source_detail.title}
|
||||
slug="date_source_detail"
|
||||
value={props.building.date_source_detail}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_source_detail.tooltip}
|
||||
/>
|
||||
<MultiDataEntry
|
||||
title={dataFields.date_link.title}
|
||||
slug="date_link"
|
||||
value={props.building.date_link}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
tooltip={dataFields.date_link.tooltip}
|
||||
placeholder="https://..."
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
const AgeContainer = withCopyEdit(AgeView);
|
||||
|
||||
export default AgeContainer;
|
||||
|
@ -31,6 +31,7 @@ const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={1}
|
||||
/>
|
||||
<DataEntry
|
||||
title={dataFields.location_street.title}
|
||||
@ -99,8 +100,10 @@ const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
value={props.building.location_latitude}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
step={0.0001}
|
||||
placeholder="51"
|
||||
step={0.00001}
|
||||
min={-90}
|
||||
max={90}
|
||||
placeholder="Latitude, e.g. 51.5467"
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
@ -109,8 +112,10 @@ const LocationView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
value={props.building.location_longitude}
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
step={0.0001}
|
||||
placeholder="0"
|
||||
step={0.00001}
|
||||
min={-180}
|
||||
max={180}
|
||||
placeholder="Longitude, e.g. -0.0586"
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</Fragment>
|
||||
|
@ -24,6 +24,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
tooltip={dataFields.size_storeys_core.tooltip}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={0}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.size_storeys_attic.title}
|
||||
@ -34,6 +35,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
tooltip={dataFields.size_storeys_attic.tooltip}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={0}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.size_storeys_basement.title}
|
||||
@ -44,6 +46,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
tooltip={dataFields.size_storeys_basement.tooltip}
|
||||
onChange={props.onChange}
|
||||
step={1}
|
||||
min={0}
|
||||
/>
|
||||
</DataEntryGroup>
|
||||
<DataEntryGroup name="Height">
|
||||
@ -55,6 +58,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.size_height_eaves.title}
|
||||
@ -65,6 +69,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
/>
|
||||
</DataEntryGroup>
|
||||
<DataEntryGroup name="Floor area">
|
||||
@ -76,6 +81,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.size_floor_area_total.title}
|
||||
@ -85,6 +91,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
/>
|
||||
</DataEntryGroup>
|
||||
<NumericDataEntry
|
||||
@ -95,6 +102,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
title={dataFields.size_plot_area_total.title}
|
||||
@ -104,6 +112,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
disabled={true}
|
||||
/>
|
||||
<NumericDataEntry
|
||||
@ -114,6 +123,7 @@ const SizeView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
step={0.1}
|
||||
min={0}
|
||||
disabled={true}
|
||||
/>
|
||||
<SelectDataEntry
|
||||
|
@ -2,6 +2,8 @@ import { parse } from 'query-string';
|
||||
import React from 'react';
|
||||
import { Link, Redirect, RouteComponentProps } 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';
|
||||
@ -10,25 +12,22 @@ import { User } from '../models/user';
|
||||
import DataEntry from './data-components/data-entry';
|
||||
import Sidebar from './sidebar';
|
||||
|
||||
interface MultiEditRouteParams {
|
||||
cat: string;
|
||||
}
|
||||
|
||||
interface MultiEditProps extends RouteComponentProps<MultiEditRouteParams> {
|
||||
interface MultiEditProps {
|
||||
user?: User;
|
||||
category: string;
|
||||
dataString: string;
|
||||
}
|
||||
|
||||
const MultiEdit: React.FC<MultiEditProps> = (props) => {
|
||||
if (!props.user){
|
||||
return <Redirect to="/sign-up.html" />;
|
||||
}
|
||||
const cat = props.match.params.cat;
|
||||
if (cat === 'like') {
|
||||
if (props.category === 'like') {
|
||||
// special case for likes
|
||||
return (
|
||||
<Sidebar>
|
||||
<section className='data-section'>
|
||||
<header className={`section-header view ${cat} background-${cat}`}>
|
||||
<header className={`section-header view ${props.category} background-${props.category}`}>
|
||||
<h2 className="h2">Like me!</h2>
|
||||
</header>
|
||||
<form className='buttons-container'>
|
||||
@ -42,34 +41,34 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
const q = parse(props.location.search);
|
||||
let data = parseJsonOrDefault(props.dataString);
|
||||
|
||||
let data: object;
|
||||
if (cat === 'like'){
|
||||
data = { like: true };
|
||||
} else {
|
||||
try {
|
||||
// TODO: verify what happens if data is string[]
|
||||
data = JSON.parse(q.data as string);
|
||||
} catch (error) {
|
||||
console.error(error, q);
|
||||
data = {};
|
||||
}
|
||||
let error: string;
|
||||
if(data == null) {
|
||||
error = 'Invalid parameters supplied';
|
||||
data = {};
|
||||
} else if(Object.values(data).some(x => x == undefined)) {
|
||||
error = 'Cannot copy empty values';
|
||||
data = {};
|
||||
}
|
||||
|
||||
return (
|
||||
<Sidebar>
|
||||
<section className='data-section'>
|
||||
<header className={`section-header view ${cat} background-${cat}`}>
|
||||
<header className={`section-header view ${props.category} background-${props.category}`}>
|
||||
<Link
|
||||
className="icon-button back"
|
||||
to={`/edit/${cat}`}>
|
||||
to={`/edit/${props.category}`}>
|
||||
<BackIcon />
|
||||
</Link>
|
||||
<h2 className="h2">Copy {cat} data</h2>
|
||||
<h2 className="h2">Copy {props.category} data</h2>
|
||||
</header>
|
||||
<form>
|
||||
<InfoBox msg='Click buildings one at a time to colour using the data below' />
|
||||
{
|
||||
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] || {};
|
||||
@ -85,8 +84,8 @@ const MultiEdit: React.FC<MultiEditProps> = (props) => {
|
||||
}
|
||||
</form>
|
||||
<form className='buttons-container'>
|
||||
<Link to={`/view/${cat}`} className='btn btn-secondary'>Back to view</Link>
|
||||
<Link to={`/edit/${cat}`} className='btn btn-secondary'>Back to edit</Link>
|
||||
<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>
|
||||
</section>
|
||||
</Sidebar>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { parse } from 'query-string';
|
||||
import { parse as parseQuery } from 'query-string';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
|
||||
import { parseJsonOrDefault } from '../helpers';
|
||||
import { strictParseInt } from '../parse';
|
||||
|
||||
import BuildingView from './building/building-view';
|
||||
@ -130,6 +131,13 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
return category;
|
||||
}
|
||||
|
||||
getMultiEditDataString(): string {
|
||||
const q = parseQuery(this.props.location.search);
|
||||
if(Array.isArray(q.data)) {
|
||||
throw new Error('Invalid format');
|
||||
} else return q.data;
|
||||
}
|
||||
|
||||
increaseRevision(revisionId) {
|
||||
revisionId = +revisionId;
|
||||
// bump revision id, only ever increasing
|
||||
@ -199,21 +207,19 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
*
|
||||
* Pulls data from URL to form update
|
||||
*
|
||||
* @param {object} building
|
||||
* @param {Building} building
|
||||
*/
|
||||
colourBuilding(building) {
|
||||
colourBuilding(building: Building) {
|
||||
const cat = this.props.match.params.category;
|
||||
const q = parse(window.location.search);
|
||||
|
||||
|
||||
if (cat === 'like') {
|
||||
this.likeBuilding(building.building_id);
|
||||
} else {
|
||||
try {
|
||||
// TODO: verify what happens if data is string[]
|
||||
const data = JSON.parse(q.data as string);
|
||||
const data = parseJsonOrDefault(this.getMultiEditDataString());
|
||||
|
||||
|
||||
if (data != undefined && !Object.values(data).some(x => x == undefined)) {
|
||||
this.updateBuilding(building.building_id, data);
|
||||
} catch (error) {
|
||||
console.error(error, q);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,7 +287,8 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
</Route>
|
||||
<Route exact path="/multi-edit/:cat" render={(props) => (
|
||||
<MultiEdit
|
||||
{...props}
|
||||
category={category}
|
||||
dataString={this.getMultiEditDataString()}
|
||||
user={this.props.user}
|
||||
/>
|
||||
)} />
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { BuildingEditSummary } from '../building/edit-history/building-edit-summary';
|
||||
import InfoBox from '../components/info-box';
|
||||
import { EditHistoryEntry } from '../models/edit-history-entry';
|
||||
|
||||
const ChangesPage = () => {
|
||||
@ -23,15 +24,18 @@ const ChangesPage = () => {
|
||||
<h1>Global edit history</h1>
|
||||
|
||||
<ul className="edit-history-list">
|
||||
{history && history.map(entry => (
|
||||
<li key={`${entry.revision_id}`} className="edit-history-list-element">
|
||||
<BuildingEditSummary
|
||||
historyEntry={entry}
|
||||
showBuildingId={true}
|
||||
hyperlinkCategories={true}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
{(history == undefined || history.length == 0) ?
|
||||
<InfoBox msg="No changes in the last week"></InfoBox> :
|
||||
history.map(entry => (
|
||||
<li key={`${entry.revision_id}`} className="edit-history-list-element">
|
||||
<BuildingEditSummary
|
||||
historyEntry={entry}
|
||||
showBuildingId={true}
|
||||
hyperlinkCategories={true}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
|
@ -6,12 +6,14 @@
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
max-height: 95%;
|
||||
max-height: calc(100%-2em);
|
||||
border-radius: 0;
|
||||
padding: 1.5em 2.5em 2.5em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.welcome-float.jumbotron {
|
||||
padding: 1em 2.5em 1.5em;
|
||||
background: #fff;
|
||||
background-color: rgba(255,255,255,0.95);
|
||||
}
|
||||
@ -23,3 +25,12 @@
|
||||
top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-float .lead {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.welcome-float .lead a {
|
||||
color: #333;
|
||||
border-bottom-color: #333;
|
||||
}
|
@ -9,18 +9,13 @@ const Welcome = () => (
|
||||
|
||||
<p className="lead">
|
||||
Colouring London is a knowledge exchange platform collecting information on every
|
||||
building in London, to help make the city more sustainable. We’re building it at The
|
||||
Bartlett Centre for Advanced Spatial Analysis, University College London.
|
||||
building in London, to help make the city more sustainable. We're developing it at University College London. Can you help us? We're looking for volunteers of all ages and abilities to help test the site and colour the buildings in.
|
||||
</p>
|
||||
<p className="lead">
|
||||
Can you help us? We’re still at an early stage of development, and we’re looking for
|
||||
volunteers of all ages and abilities to test and provide feedback on the site as we
|
||||
build it.
|
||||
Our building data comes from many different sources. Though we are unable to vouch for their 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.
|
||||
</p>
|
||||
<p className="lead">
|
||||
All of the data we collect is made <Link to="/data-extracts.html">openly available</Link> –
|
||||
please read our <a href="https://www.pages.colouring.london/data-ethics">data ethics policy</a> and
|
||||
credit Colouring London if you use or share our maps or data.
|
||||
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>
|
||||
<Link to="/view/categories"
|
||||
className="btn btn-outline-dark btn-lg btn-block">
|
||||
|
@ -13,3 +13,13 @@ export function dateReviver(name, value) {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
export function parseJsonOrDefault(jsonString: string) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
4
migrations/014.block-users.down.sql
Normal file
4
migrations/014.block-users.down.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE users
|
||||
DROP COLUMN is_blocked,
|
||||
DROP COLUMN blocked_on,
|
||||
DROP COLUMN blocked_reason;
|
4
migrations/014.block-users.up.sql
Normal file
4
migrations/014.block-users.up.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE users
|
||||
ADD COLUMN is_blocked BOOLEAN NOT NULL DEFAULT (false),
|
||||
ADD COLUMN blocked_on TIMESTAMP WITH TIME ZONE NULL,
|
||||
ADD COLUMN blocked_reason TEXT NULL;
|
Loading…
Reference in New Issue
Block a user