Store only current edits in data container state

This commit is contained in:
Maciej Ziarkowski 2019-10-15 19:16:48 +01:00
parent 0a1b41cc07
commit b81d49df43
4 changed files with 78 additions and 45 deletions

View File

@ -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<BuildingViewProps> = (props) => {
case 'location':
return <LocationContainer
{...props}
key={props.building && props.building.building_id}
title="Location"
help="https://pages.colouring.london/location"
intro="Where are the buildings? Address, location and cross-references."
@ -44,7 +43,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'use':
return <UseContainer
{...props}
key={props.building && props.building.building_id}
inactive={true}
title="Land Use"
intro="How are buildings used, and how does use change over time? Coming soon…"
@ -53,7 +51,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'type':
return <TypeContainer
{...props}
key={props.building && props.building.building_id}
inactive={false}
title="Type"
intro="How were buildings previously used?"
@ -62,7 +59,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'age':
return <AgeContainer
{...props}
key={props.building && props.building.building_id}
title="Age"
help="https://pages.colouring.london/age"
intro="Building age data can support energy analysis and help predict long-term change."
@ -70,7 +66,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'size':
return <SizeContainer
{...props}
key={props.building && props.building.building_id}
title="Size &amp; Shape"
intro="How big are buildings?"
help="https://pages.colouring.london/shapeandsize"
@ -78,7 +73,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'construction':
return <ConstructionContainer
{...props}
key={props.building && props.building.building_id}
title="Construction"
intro="How are buildings built? Coming soon…"
help="https://pages.colouring.london/construction"
@ -87,7 +81,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'team':
return <TeamContainer
{...props}
key={props.building && props.building.building_id}
title="Team"
intro="Who built the buildings? Coming soon…"
help="https://pages.colouring.london/team"
@ -96,7 +89,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'sustainability':
return <SustainabilityContainer
{...props}
key={props.building && props.building.building_id}
title="Sustainability"
intro="Are buildings energy efficient?"
help="https://pages.colouring.london/sustainability"
@ -105,7 +97,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'streetscape':
return <StreetscapeContainer
{...props}
key={props.building && props.building.building_id}
title="Streetscape"
intro="What's the building's context? Coming soon…"
help="https://pages.colouring.london/streetscape"
@ -114,7 +105,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'community':
return <CommunityContainer
{...props}
key={props.building && props.building.building_id}
title="Community"
intro="How does this building work for the local community?"
help="https://pages.colouring.london/community"
@ -123,7 +113,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'planning':
return <PlanningContainer
{...props}
key={props.building && props.building.building_id}
title="Planning"
intro="Planning controls relating to protection and reuse."
help="https://pages.colouring.london/planning"
@ -131,7 +120,6 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
case 'like':
return <LikeContainer
{...props}
key={props.building && props.building.building_id}
title="Like Me!"
intro="Do you like the building and think it contributes to the city?"
help="https://pages.colouring.london/likeme"

View File

@ -29,7 +29,7 @@ interface DataTitleCopyableProps {
tooltip: string;
slug: string;
disabled?: boolean;
copy: any; // TODO: type should be CopyProps, but that clashes with propTypes in some obscure way
copy?: any; // TODO: type should be CopyProps, but that clashes with propTypes in some obscure way
}
const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (props) => { // TODO: remove any

View File

@ -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<Building>;
}
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 <Redirect to="/sign-up.html" />
}
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) => {
<Fragment>
<ErrorBox msg={this.state.error} />
{
this.props.cat === 'like' ? // special-case for likes
null :
this.isEdited() && this.props.cat !== 'like' ? // special-case for likes
<div className="buttons-container with-space">
<button
type="submit"
className="btn btn-primary">
Save
</button>
</div>
</div> :
null
}
</Fragment>
: null
}
<WrappedComponent
building={this.state.building}
building={currentBuilding}
building_like={this.props.building_like}
mode={this.props.mode}
copy={copy}

View File

@ -36,4 +36,16 @@ function sanitiseURL(string){
return `${url_.protocol}//${url_.hostname}${url_.pathname || ''}${url_.search || ''}${url_.hash || ''}`
}
export { sanitiseURL }
function compareObjects(objA: object, objB: object): [object, object] {
const reverse = {}
const forward = {}
for (const [key, value] of Object.entries(objB)) {
if (objA[key] !== value) {
reverse[key] = objA[key];
forward[key] = value;
}
}
return [forward, reverse];
}
export { sanitiseURL, compareObjects }