Merge pull request #521 from mz8i/feature/517-prevent-copy-empty

Feature 517: prevent copying empty values
This commit is contained in:
Maciej Ziarkowski 2019-11-27 14:01:33 +01:00 committed by GitHub
commit 56b1aafb98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 59 additions and 43 deletions

View File

@ -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">

View File

@ -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"

View File

@ -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')?

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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>

View File

@ -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}
/>
)} />

View File

@ -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;
}
}