From ce473cb453172834efa379cd3253fd93ea24369e Mon Sep 17 00:00:00 2001
From: Maciej Ziarkowski
Date: Tue, 15 Oct 2019 13:20:09 +0100
Subject: [PATCH 01/19] Fix checkbox inputs
---
.../building/data-containers/planning.tsx | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/app/src/frontend/building/data-containers/planning.tsx b/app/src/frontend/building/data-containers/planning.tsx
index 3a093138..1457010d 100644
--- a/app/src/frontend/building/data-containers/planning.tsx
+++ b/app/src/frontend/building/data-containers/planning.tsx
@@ -26,7 +26,7 @@ const PlanningView = (props) => (
value={props.building.planning_in_conservation_area}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_in_list}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_in_glher}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_in_apa}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_in_local_list}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_in_historic_area_assessment}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
/>
(
value={props.building.planning_demolition_proposed}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
disabled={true}
/>
(
value={props.building.planning_demolition_complete}
mode={props.mode}
copy={props.copy}
- onChange={props.onChange}
+ onChange={props.onCheck}
disabled={true}
/>
Date: Tue, 15 Oct 2019 14:37:23 +0100
Subject: [PATCH 02/19] Type, simplify, fix data containers
This contains a couple fixes for minor bugs
that were discovered after adding static types
to the category data editing code.
The other changes are mostly refactoring and styling
---
app/src/frontend/building/building-view.tsx | 13 +-
.../frontend/building/container-header.tsx | 12 +-
app/src/frontend/building/data-container.tsx | 125 ++++++++++--------
app/src/frontend/building/sidebar.css | 9 +-
app/src/frontend/map-app.tsx | 4 +-
app/src/frontend/models/building.ts | 8 ++
app/src/frontend/models/user.ts | 8 ++
7 files changed, 115 insertions(+), 64 deletions(-)
create mode 100644 app/src/frontend/models/building.ts
create mode 100644 app/src/frontend/models/user.ts
diff --git a/app/src/frontend/building/building-view.tsx b/app/src/frontend/building/building-view.tsx
index 0b7a97a2..753973b8 100644
--- a/app/src/frontend/building/building-view.tsx
+++ b/app/src/frontend/building/building-view.tsx
@@ -14,13 +14,24 @@ import StreetscapeContainer from './data-containers/streetscape';
import CommunityContainer from './data-containers/community';
import PlanningContainer from './data-containers/planning';
import LikeContainer from './data-containers/like';
+import { Building } from '../models/building';
+
+
+interface BuildingViewProps {
+ cat: string;
+ mode: 'view' | 'edit' | 'multi-edit';
+ building: Building;
+ building_like: boolean;
+ user: any;
+ selectBuilding: (building:any) => void
+}
/**
* Top-level container for building view/edit form
*
* @param props
*/
-const BuildingView = (props) => {
+const BuildingView: React.FunctionComponent = (props) => {
switch (props.cat) {
case 'location':
return = (props) => (
+const ContainerHeader: React.FunctionComponent = (props) => (
diff --git a/app/src/frontend/building/data-container.tsx b/app/src/frontend/building/data-container.tsx
index 1547f9fc..66bcb098 100644
--- a/app/src/frontend/building/data-container.tsx
+++ b/app/src/frontend/building/data-container.tsx
@@ -5,6 +5,28 @@ import { Redirect } from 'react-router-dom';
import ContainerHeader from './container-header';
import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
+import { Building } from '../models/building';
+import { User } from '../models/user';
+
+interface DataContainerProps {
+ title: string;
+ cat: string;
+ intro: string;
+ help: string;
+ inactive?: boolean;
+
+ user: User;
+ mode: 'view' | 'edit' | 'multi-edit';
+ building: Building;
+ building_like: boolean;
+}
+
+interface DataContainerState {
+ error: string;
+ copying: boolean;
+ keys_to_copy: object;
+ building: Building
+}
/**
* Shared functionality for view/edit forms
@@ -15,14 +37,15 @@ import InfoBox from '../components/info-box';
* @param WrappedComponent
*/
const withCopyEdit = (WrappedComponent) => {
- return class extends React.Component { // TODO: add proper types
+ return class DataContainer extends React.Component { // TODO: add proper types
+ static displayName = 'DataContainer';
+
static propTypes = { // TODO: generate propTypes from TS
title: PropTypes.string,
slug: PropTypes.string,
intro: PropTypes.string,
help: PropTypes.string,
inactive: PropTypes.bool,
- building_id: PropTypes.number,
children: PropTypes.node
};
@@ -30,8 +53,7 @@ const withCopyEdit = (WrappedComponent) => {
super(props);
this.state = {
- error: this.props.error || undefined,
- like: this.props.like || undefined,
+ error: undefined,
copying: false,
keys_to_copy: {},
building: this.props.building
@@ -62,7 +84,7 @@ const withCopyEdit = (WrappedComponent) => {
* @param {string} key
*/
toggleCopyAttribute(key: string) {
- const keys = this.state.keys_to_copy;
+ const keys = {...this.state.keys_to_copy};
if(this.state.keys_to_copy[key]){
delete keys[key];
} else {
@@ -181,7 +203,7 @@ const withCopyEdit = (WrappedComponent) => {
}
render() {
- if (this.state.mode === 'edit' && !this.props.user){
+ if (this.props.mode === 'edit' && !this.props.user){
return
}
@@ -198,60 +220,17 @@ const withCopyEdit = (WrappedComponent) => {
}
return (
+
{
- this.props.building != undefined ?
-
- :
-
:
- }
-
}
+
);
}
diff --git a/app/src/frontend/building/sidebar.css b/app/src/frontend/building/sidebar.css
index 53caf48e..7ce6542c 100644
--- a/app/src/frontend/building/sidebar.css
+++ b/app/src/frontend/building/sidebar.css
@@ -139,6 +139,11 @@
/**
* Data list sections
*/
+
+ .section-body {
+ margin-top: 0.75em;
+ padding: 0 0.75em;
+ }
.data-section .h3 {
margin: 0;
}
@@ -162,9 +167,7 @@
padding-left: 0.75rem;
padding-right: 0.75rem;
}
-.data-section form {
- padding: 0 0.75rem;
-}
+
.data-list a {
color: #555;
}
diff --git a/app/src/frontend/map-app.tsx b/app/src/frontend/map-app.tsx
index 5191f3fe..4dcd2b76 100644
--- a/app/src/frontend/map-app.tsx
+++ b/app/src/frontend/map-app.tsx
@@ -199,7 +199,7 @@ class MapApp extends React.Component {
}
render() {
- const mode = this.props.match.params.mode || 'basic';
+ const mode = this.props.match.params.mode;
let category = this.state.category || 'age';
@@ -240,7 +240,7 @@ class MapApp extends React.Component {
Date: Tue, 15 Oct 2019 14:38:07 +0100
Subject: [PATCH 03/19] Improve scroll area UI
---
app/src/frontend/building/sidebar.css | 10 +++++++++-
app/src/frontend/pages/welcome.css | 2 +-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/src/frontend/building/sidebar.css b/app/src/frontend/building/sidebar.css
index 7ce6542c..95f829a4 100644
--- a/app/src/frontend/building/sidebar.css
+++ b/app/src/frontend/building/sidebar.css
@@ -5,7 +5,7 @@
order: 1;
padding: 0 0 2em;
background: #fff;
- overflow-y: scroll;
+ overflow-y: auto;
height: 40%;
}
@@ -34,6 +34,14 @@
text-decoration: none;
color: #222;
padding: 0.75rem 0.25rem 0.5rem 0;
+ z-index: 1000;
+}
+
+@media (min-width: 768px) {
+ .section-header {
+ position: sticky;
+ top: 0;
+ }
}
.section-header h2,
.section-header .icon-buttons {
diff --git a/app/src/frontend/pages/welcome.css b/app/src/frontend/pages/welcome.css
index d43293a7..88095379 100644
--- a/app/src/frontend/pages/welcome.css
+++ b/app/src/frontend/pages/welcome.css
@@ -9,7 +9,7 @@
max-height: 100%;
border-radius: 0;
padding: 1.5em 2.5em 2.5em;
- overflow-y: scroll;
+ overflow-y: auto;
}
.welcome-float.jumbotron {
background: #fff;
From f498f4730b23ddd7bacf6171cccc09713c4057c0 Mon Sep 17 00:00:00 2001
From: Maciej Ziarkowski
Date: Tue, 15 Oct 2019 14:53:01 +0100
Subject: [PATCH 04/19] Fix view/edit route redirect
---
app/src/frontend/app.tsx | 2 +-
app/src/frontend/map-app.tsx | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/src/frontend/app.tsx b/app/src/frontend/app.tsx
index 5e681574..f00428cb 100644
--- a/app/src/frontend/app.tsx
+++ b/app/src/frontend/app.tsx
@@ -105,7 +105,7 @@ class App extends React.Component { // TODO: add proper types
- (
+ (
{
/>
-
-
-
+ ()}
+ />
Date: Tue, 15 Oct 2019 15:44:22 +0100
Subject: [PATCH 05/19] Add DataTitle types
---
.../building/data-components/data-title.tsx | 22 ++++++++++++++++---
app/src/frontend/building/data-container.tsx | 17 +++++++++++---
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/app/src/frontend/building/data-components/data-title.tsx b/app/src/frontend/building/data-components/data-title.tsx
index f36022c3..d108adf3 100644
--- a/app/src/frontend/building/data-components/data-title.tsx
+++ b/app/src/frontend/building/data-components/data-title.tsx
@@ -3,7 +3,13 @@ import PropTypes from 'prop-types';
import Tooltip from '../../components/tooltip';
-const DataTitle: React.FunctionComponent = (props) => {
+
+interface DataTitleProps {
+ title: string;
+ tooltip: string;
+}
+
+const DataTitle: React.FunctionComponent = (props) => {
return (
{ props.title }
@@ -17,7 +23,16 @@ DataTitle.propTypes = {
tooltip: PropTypes.string
}
-const DataTitleCopyable: React.FunctionComponent = (props) => { // TODO: remove any
+
+interface DataTitleCopyableProps {
+ title: string;
+ tooltip: string;
+ slug: string;
+ disabled?: boolean;
+ copy: any; // TODO: type should be CopyProps, but that clashes with propTypes in some obscure way
+}
+
+const DataTitleCopyable: React.FunctionComponent = (props) => { // TODO: remove any
return (
{ props.tooltip?
: null }
@@ -48,7 +63,8 @@ DataTitleCopyable.propTypes = {
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
- toggleCopyAttribute: PropTypes.func
+ toggleCopyAttribute: PropTypes.func,
+ toggleCopying: PropTypes.func
})
}
diff --git a/app/src/frontend/building/data-container.tsx b/app/src/frontend/building/data-container.tsx
index 66bcb098..821a4374 100644
--- a/app/src/frontend/building/data-container.tsx
+++ b/app/src/frontend/building/data-container.tsx
@@ -24,10 +24,17 @@ interface DataContainerProps {
interface DataContainerState {
error: string;
copying: boolean;
- keys_to_copy: object;
+ keys_to_copy: {[key: string]: boolean};
building: Building
}
+interface CopyProps {
+ copying: boolean;
+ toggleCopying: () => void;
+ toggleCopyAttribute: (key: string) => void;
+ copyingKey: (key: string) => boolean;
+}
+
/**
* Shared functionality for view/edit forms
*
@@ -212,11 +219,11 @@ const withCopyEdit = (WrappedComponent) => {
values_to_copy[key] = this.state.building[key]
}
const data_string = JSON.stringify(values_to_copy);
- const copy = {
+ const copy: CopyProps = {
copying: this.state.copying,
toggleCopying: this.toggleCopying,
toggleCopyAttribute: this.toggleCopyAttribute,
- copyingKey: (key) => this.state.keys_to_copy[key]
+ copyingKey: (key: string) => this.state.keys_to_copy[key]
}
return (
{
}
export default withCopyEdit;
+
+export {
+ CopyProps
+};
From b81d49df4370ca3c5a47c414207c560e6288f24c Mon Sep 17 00:00:00 2001
From: Maciej Ziarkowski
Date: Tue, 15 Oct 2019 19:16:48 +0100
Subject: [PATCH 06/19] Store only current edits in data container state
---
app/src/frontend/building/building-view.tsx | 14 +--
.../building/data-components/data-title.tsx | 2 +-
app/src/frontend/building/data-container.tsx | 93 +++++++++++++------
app/src/frontend/helpers.ts | 14 ++-
4 files changed, 78 insertions(+), 45 deletions(-)
diff --git a/app/src/frontend/building/building-view.tsx b/app/src/frontend/building/building-view.tsx
index 753973b8..d322d984 100644
--- a/app/src/frontend/building/building-view.tsx
+++ b/app/src/frontend/building/building-view.tsx
@@ -23,7 +23,7 @@ interface BuildingViewProps {
building: Building;
building_like: boolean;
user: any;
- selectBuilding: (building:any) => void
+ selectBuilding: (building: Building) => void
}
/**
@@ -36,7 +36,6 @@ const BuildingView: React.FunctionComponent = (props) => {
case 'location':
return = (props) => {
case 'use':
return = (props) => {
case 'type':
return = (props) => {
case 'age':
return = (props) => {
case 'size':
return = (props) => {
case 'construction':
return = (props) => {
case 'team':
return = (props) => {
case 'sustainability':
return = (props) => {
case 'streetscape':
return = (props) => {
case 'community':
return = (props) => {
case 'planning':
return = (props) => {
case 'like':
return = (props) => { // TODO: remove any
diff --git a/app/src/frontend/building/data-container.tsx b/app/src/frontend/building/data-container.tsx
index 821a4374..f3955f36 100644
--- a/app/src/frontend/building/data-container.tsx
+++ b/app/src/frontend/building/data-container.tsx
@@ -7,6 +7,7 @@ import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
import { Building } from '../models/building';
import { User } from '../models/user';
+import { compareObjects } from '../helpers';
interface DataContainerProps {
title: string;
@@ -19,13 +20,15 @@ interface DataContainerProps {
mode: 'view' | 'edit' | 'multi-edit';
building: Building;
building_like: boolean;
+ selectBuilding: (building: Building) => void
}
interface DataContainerState {
error: string;
copying: boolean;
keys_to_copy: {[key: string]: boolean};
- building: Building
+ currentBuildingId: number;
+ buildingEdits: Partial;
}
interface CopyProps {
@@ -63,7 +66,8 @@ const withCopyEdit = (WrappedComponent) => {
error: undefined,
copying: false,
keys_to_copy: {},
- building: this.props.building
+ buildingEdits: {},
+ currentBuildingId: undefined
};
this.handleChange = this.handleChange.bind(this);
@@ -76,6 +80,17 @@ const withCopyEdit = (WrappedComponent) => {
this.toggleCopyAttribute = this.toggleCopyAttribute.bind(this);
}
+ static getDerivedStateFromProps(props, state) {
+ if(props.building != undefined && props.building.building_id !== state.currentBuildingId) {
+ return {
+ buildingEdits: {},
+ currentBuildingId: props.building.building_id
+ };
+ }
+
+ return null;
+ }
+
/**
* Enter or exit "copying" state - allow user to select attributes to copy
*/
@@ -102,18 +117,33 @@ const withCopyEdit = (WrappedComponent) => {
})
}
- updateBuildingState(key, value) {
- const building = {...this.state.building};
- building[key] = value;
+ isEdited() {
+ const edits = this.state.buildingEdits;
+ // check if the edits object has any fields
+ return Object.entries(edits).length !== 0;
+ }
+
+ getEditedBuilding() {
+ if(this.isEdited()) {
+ return Object.assign({}, this.props.building, this.state.buildingEdits);
+ } else {
+ return {...this.props.building};
+ }
+ }
+
+ updateBuildingState(key: string, value: any) {
+ const newBuilding = this.getEditedBuilding();
+ newBuilding[key] = value;
+ const [forwardPatch] = compareObjects(this.props.building, newBuilding);
this.setState({
- building: building
+ buildingEdits: forwardPatch
});
}
/**
* Handle changes on typical inputs
- * - e.g. input[type=text], radio, select, textare
+ * - e.g. input[type=text], radio, select, textarea
*
* @param {*} event
*/
@@ -185,28 +215,29 @@ const withCopyEdit = (WrappedComponent) => {
);
}
- handleSubmit(event) {
+ async handleSubmit(event) {
event.preventDefault();
- this.setState({error: undefined})
+ this.setState({error: undefined});
- fetch(`/api/buildings/${this.props.building.building_id}.json`, {
- method: 'POST',
- body: JSON.stringify(this.state.building),
- headers:{
- 'Content-Type': 'application/json'
- },
- credentials: 'same-origin'
- }).then(
- res => res.json()
- ).then(function(res){
- if (res.error) {
- this.setState({error: res.error})
+ try {
+ const res = await fetch(`/api/buildings/${this.props.building.building_id}.json`, {
+ method: 'POST',
+ body: JSON.stringify(this.state.buildingEdits),
+ headers:{
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin'
+ });
+ const data = await res.json();
+
+ if (data.error) {
+ this.setState({error: data.error})
} else {
- this.props.selectBuilding(res);
+ this.props.selectBuilding(data);
}
- }.bind(this)).catch(
- (err) => this.setState({error: err})
- );
+ } catch(err) {
+ this.setState({error: err});
+ }
}
render() {
@@ -214,9 +245,11 @@ const withCopyEdit = (WrappedComponent) => {
return
}
+ const currentBuilding = this.getEditedBuilding();
+
const values_to_copy = {}
for (const key of Object.keys(this.state.keys_to_copy)) {
- values_to_copy[key] = this.state.building[key]
+ values_to_copy[key] = currentBuilding[key]
}
const data_string = JSON.stringify(values_to_copy);
const copy: CopyProps = {
@@ -262,21 +295,21 @@ const withCopyEdit = (WrappedComponent) => {
{
- this.props.cat === 'like' ? // special-case for likes
- null :
+ this.isEdited() && this.props.cat !== 'like' ? // special-case for likes
-
+ :
+ null
}
: null
}
Date: Wed, 16 Oct 2019 13:11:25 +0100
Subject: [PATCH 07/19] Clear all state fields on select change
---
app/src/frontend/building/data-container.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/src/frontend/building/data-container.tsx b/app/src/frontend/building/data-container.tsx
index f3955f36..593c7112 100644
--- a/app/src/frontend/building/data-container.tsx
+++ b/app/src/frontend/building/data-container.tsx
@@ -81,10 +81,14 @@ const withCopyEdit = (WrappedComponent) => {
}
static getDerivedStateFromProps(props, state) {
- if(props.building != undefined && props.building.building_id !== state.currentBuildingId) {
+ const newBuildingId = props.building == undefined ? undefined : props.building.building_id;
+ if(newBuildingId !== state.currentBuildingId) {
return {
+ error: undefined,
+ copying: false,
+ keys_to_copy: {},
buildingEdits: {},
- currentBuildingId: props.building.building_id
+ currentBuildingId: newBuildingId
};
}
From 9d4d24aefc34bce753ed1a766aca23c427d7f9ae Mon Sep 17 00:00:00 2001
From: Maciej Ziarkowski
Date: Wed, 16 Oct 2019 13:30:59 +0100
Subject: [PATCH 08/19] Fix label click on like checkbox with non-uniq id
The label for the like checkbox was not clickable because the ID #like
was not unique on the website. The ID has been changed to like_check
to avoid that.
---
app/src/frontend/building/data-components/like-data-entry.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/frontend/building/data-components/like-data-entry.tsx b/app/src/frontend/building/data-components/like-data-entry.tsx
index 201f6e83..1e2c7c0c 100644
--- a/app/src/frontend/building/data-components/like-data-entry.tsx
+++ b/app/src/frontend/building/data-components/like-data-entry.tsx
@@ -29,12 +29,12 @@ const LikeDataEntry: React.FunctionComponent = (props) => { // TODO: remove
}
-