Store only current edits in data container state
This commit is contained in:
parent
0a1b41cc07
commit
b81d49df43
@ -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 & 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"
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user