Skip to content

Commit 09c8f23

Browse files
authored
bookmark poc (#1482)
* bookmarking
1 parent b46e69c commit 09c8f23

File tree

8 files changed

+204
-8
lines changed

8 files changed

+204
-8
lines changed

client/Routes.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@ import About from '@components/main/About';
1717
import Research from '@components/main/Research';
1818
import Contact from '@components/contact/Contact';
1919
import ContentBottom from '@components/common/ContentBottom';
20+
import queryString from 'query-string';
2021

2122
export default function Routes() {
22-
const { pathname } = useLocation();
23+
const { pathname, search } = useLocation();
24+
const values = queryString.parse(search);
2325

2426
return (
2527
<>
2628
{/* Dark Theme - Map. */}
2729
<ThemeProvider theme={darkTheme}>
2830
<Paper elevation={0}>
2931
<Box visibility={pathname !== '/map' ? 'hidden' : 'visible'}>
30-
<Desktop />
32+
<Desktop
33+
initialState={values}
34+
/>
3135
</Box>
3236
</Paper>
3337
</ThemeProvider>
@@ -43,7 +47,7 @@ export default function Routes() {
4347
<Route path="/about" component={About} />
4448
<Route path="/contact" component={Contact} />
4549
<Route path="/">
46-
<Redirect to="map" />
50+
<Redirect to={`map${search}`} />
4751
</Route>
4852
</Switch>
4953
<ContentBottom />

client/components/Map/Map.jsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ class Map extends React.Component {
125125
this.requestDetail = null;
126126
this.popup = null;
127127
this.isSubscribed = null;
128+
this.initialState = props.initialState
129+
this.hasSetInitialNCView = false
130+
128131
}
129132

130133
componentDidMount() {
@@ -153,6 +156,7 @@ class Map extends React.Component {
153156
}
154157
});
155158
this.map = map;
159+
156160
}
157161

158162
componentWillUnmount() {
@@ -229,6 +233,27 @@ class Map extends React.Component {
229233
this.setState({ selectedNc: nc });
230234
return this.ncLayer.selectRegion(ncId);
231235
}
236+
237+
238+
const { dispatchUpdateNcId,dispatchUpdateSelectedCouncils,dispatchUpdateUnselectedCouncils, councils, ncBoundaries } = this.props;
239+
240+
if(this.initialState.councilId && councils?.length > 0 && !(this.hasSetInitialNCView) && ncBoundaries){
241+
try{
242+
const selectedCouncilId = Number(this.initialState.councilId);
243+
const newSelectedCouncil = councils.find(({ councilId }) => councilId === selectedCouncilId);
244+
if (!newSelectedCouncil){
245+
throw new Error('Council Does not exist from search query')
246+
}
247+
const newSelected = [newSelectedCouncil];
248+
dispatchUpdateSelectedCouncils(newSelected);
249+
dispatchUpdateUnselectedCouncils(councils);
250+
dispatchUpdateNcId(selectedCouncilId);
251+
this.hasSetInitialNCView = true
252+
} catch (err) {
253+
console.log("could not set ncid")
254+
this.hasSetInitialNCView = false
255+
}
256+
}
232257
}
233258

234259
initLayers = addListeners => {

client/components/Map/index.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { connect } from 'react-redux';
66
import { withStyles } from '@material-ui/core/styles';
77
import axios from 'axios';
88
import { getDataRequest, getDataRequestSuccess, updateDateRanges } from '@reducers/data';
9+
import { updateStartDate, updateEndDate, updateNcId, updateRequestTypes} from '@reducers/filters'
910
import { updateMapPosition } from '@reducers/ui';
1011
import { trackMapExport } from '@reducers/analytics';
1112
import { INTERNAL_DATE_SPEC } from '../common/CONSTANTS';
@@ -41,9 +42,12 @@ class MapContainer extends React.Component {
4142
// converted and stored in the Redux store.
4243
this.rawRequests = [];
4344
this.isSubscribed = null;
45+
46+
this.initialState = props.initialState
4447
}
4548

46-
componentDidMount() {
49+
componentDidMount(props) {
50+
this.processSearchParams()
4751
this.isSubscribed = true;
4852
this.setData();
4953
}
@@ -65,6 +69,31 @@ class MapContainer extends React.Component {
6569
this.isSubscribed = false;
6670
}
6771

72+
73+
processSearchParams = () => {
74+
// Dispatch to edit Redux store with url search params
75+
const { dispatchUpdateStartDate, dispatchUpdateEndDate, dispatchUpdateTypesFilter } = this.props;
76+
77+
// Filter requests on time
78+
const dateFormat = 'YYYY-MM-DD';
79+
// TODO: Check if endDate > startDate
80+
if (moment(this.initialState.startDate, 'YYYY-MM-DD', true).isValid() && moment(this.initialState.endDate, 'YYYY-MM-DD',true).isValid()){
81+
const formattedStart = moment(this.initialState.startDate).format(dateFormat);
82+
const formattedEnd = moment(this.initialState.endDate).format(dateFormat);
83+
if (formattedStart <= formattedEnd){
84+
dispatchUpdateStartDate(formattedStart);
85+
dispatchUpdateEndDate(formattedEnd);
86+
}
87+
}
88+
89+
for(let request_id = 1; request_id < 13; request_id++){
90+
if (this.initialState[`rtId${request_id}`] == 'false'){
91+
dispatchUpdateTypesFilter(request_id);
92+
}
93+
}
94+
95+
}
96+
6897
/**
6998
* Returns the non-overlapping date ranges of A before and after B.
7099
* @param {string} startA The start date of range A in INTERNAL_DATE_SPEC format.
@@ -301,6 +330,7 @@ class MapContainer extends React.Component {
301330
updatePosition={updatePosition}
302331
exportMap={exportMap}
303332
selectedTypes={selectedTypes}
333+
initialState={this.initialState}
304334
/>
305335
<CookieNotice />
306336
{isMapLoading && <img style={{ width:window.innerWidth, height: 16, position:'absolute' }} src={gif}/>}
@@ -328,6 +358,10 @@ const mapDispatchToProps = dispatch => ({
328358
getDataRedux: () => dispatch(getDataRequest()),
329359
getDataSuccess: data => dispatch(getDataRequestSuccess(data)),
330360
updateDateRangesWithRequests: dateRanges => dispatch(updateDateRanges(dateRanges)),
361+
dispatchUpdateStartDate: startDate => dispatch(updateStartDate(startDate)),
362+
dispatchUpdateEndDate: endDate => dispatch(updateEndDate(endDate)),
363+
dispatchUpdateNcId: id => dispatch(updateNcId(id)),
364+
dispatchUpdateTypesFilter: type => dispatch(updateRequestTypes(type))
331365
});
332366

333367
MapContainer.propTypes = {};

client/components/Map/layers/RequestsLayer.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,12 @@ class RequestsLayer extends React.Component {
228228
};
229229

230230
setFilters = (selectedTypes, requestStatus, startDate, endDate) => {
231-
this.map.setFilter('request-circles',
231+
if (this.map){
232+
this.map.setFilter('request-circles',
232233
this.getFilterSpec(selectedTypes, requestStatus, startDate, endDate));
233234
// Currently, we do not support heatmap. If we did, we'd want to update
234235
// its filter here as well.
236+
}
235237
};
236238

237239
setRequests = requests => {

client/components/main/Desktop/FilterMenu.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import DateSelector from '@components/DateSelector/DateSelector';
1515
import TypeSelector from '@components/main/Desktop/TypeSelector';
1616
import StatusSelector from '@components/main/Desktop/StatusSelector';
1717
import CouncilSelector from '@components/main/Desktop/CouncilSelector';
18+
import ShareableLinkCreator from '@components/main/Desktop/ShareableLinkCreator';
19+
1820
// import GearButton from '@components/common/GearButton';
1921
// import clsx from 'clsx';
2022

@@ -114,6 +116,9 @@ const FilterMenu = ({ resetMap }) => {
114116
<div className={classes.selectorWrapper}>
115117
<StatusSelector />
116118
</div>
119+
<div className={classes.selectorWrapper}>
120+
<ShareableLinkCreator />
121+
</div>
117122
</CardContent>
118123
</Collapse>
119124
</Card>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { connect } from 'react-redux';
3+
import Button from '@material-ui/core/Button';
4+
import PropTypes from 'prop-types';
5+
6+
const ShareableLinkCreator = ({
7+
requestStatus,
8+
}) => (
9+
<>
10+
<Button
11+
variant="contained"
12+
onClick={() => {
13+
// const url = new URL(`${process.env.API_URL}/map`);
14+
const url = new URL(`${window.location.href.split('?')[0]}`);
15+
if (requestStatus.councilId) {
16+
url.searchParams.append('councilId', requestStatus.councilId);
17+
}
18+
for (let requestTypeIndex = 1; requestTypeIndex < 13; requestTypeIndex += 1) {
19+
if (requestStatus.requestTypes[requestTypeIndex] === false) {
20+
url.searchParams.append(`rtId${requestTypeIndex}`, requestStatus.requestTypes[requestTypeIndex]);
21+
}
22+
}
23+
url.searchParams.append('requestStatusOpen', requestStatus.requestStatus.open);
24+
url.searchParams.append('requestStatusClosed', requestStatus.requestStatus.closed);
25+
url.searchParams.append('startDate', requestStatus.startDate);
26+
url.searchParams.append('endDate', requestStatus.endDate);
27+
navigator.clipboard.writeText(url);
28+
}}
29+
>
30+
Get Shareable Link
31+
</Button>
32+
</>
33+
);
34+
35+
const mapStateToProps = state => ({
36+
requestStatus: state.filters,
37+
});
38+
39+
export default connect(
40+
mapStateToProps,
41+
)(ShareableLinkCreator);
42+
43+
ShareableLinkCreator.propTypes = {
44+
requestStatus: PropTypes.shape({
45+
requestStatus: PropTypes.shape({
46+
open: PropTypes.bool.isRequired,
47+
closed: PropTypes.bool.isRequired,
48+
}).isRequired,
49+
startDate: PropTypes.string,
50+
endDate: PropTypes.string,
51+
councilId: PropTypes.number,
52+
requestTypes: PropTypes.shape({
53+
1: PropTypes.bool,
54+
2: PropTypes.bool,
55+
3: PropTypes.bool,
56+
4: PropTypes.bool,
57+
5: PropTypes.bool,
58+
6: PropTypes.bool,
59+
7: PropTypes.bool,
60+
8: PropTypes.bool,
61+
9: PropTypes.bool,
62+
10: PropTypes.bool,
63+
11: PropTypes.bool,
64+
12: PropTypes.bool,
65+
}),
66+
}).isRequired,
67+
};

client/components/main/Desktop/index.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { makeStyles } from '@material-ui/core/styles';
33

44
import MapContainer from '@components/Map';
5+
import PropTypes from 'prop-types';
56
import PersistentDrawerLeft from '../shared/PersistentDrawerLeft';
67

78
const useStyles = makeStyles(theme => ({
@@ -14,15 +15,38 @@ const useStyles = makeStyles(theme => ({
1415
},
1516
}));
1617

17-
const Desktop = () => {
18+
const Desktop = ({ initialState }) => {
1819
const classes = useStyles();
19-
2020
return (
2121
<div className={classes.root}>
2222
<PersistentDrawerLeft />
23-
<MapContainer />
23+
<MapContainer
24+
initialState={initialState}
25+
/>
2426
</div>
2527
);
2628
};
2729

2830
export default Desktop;
31+
32+
Desktop.propTypes = {
33+
initialState: PropTypes.shape({
34+
councilId: PropTypes.string,
35+
rtId1: PropTypes.string,
36+
rtId2: PropTypes.string,
37+
rtId3: PropTypes.string,
38+
rtId4: PropTypes.string,
39+
rtId5: PropTypes.string,
40+
rtId6: PropTypes.string,
41+
rtId7: PropTypes.string,
42+
rtId8: PropTypes.string,
43+
rtId9: PropTypes.string,
44+
rtId10: PropTypes.string,
45+
rtId11: PropTypes.string,
46+
rtId12: PropTypes.string,
47+
requestStatusOpen: PropTypes.string,
48+
requestStatusClosed: PropTypes.string,
49+
startDate: PropTypes.string,
50+
endDate: PropTypes.string,
51+
}).isRequired,
52+
};

client/redux/reducers/filters.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,36 @@ const initialState = {
7777
};
7878

7979
export default (state = initialState, action) => {
80+
const url = new URL(window.location.href);
81+
const newSearchParams = url.searchParams;
82+
8083
switch (action.type) {
8184
case types.UPDATE_START_DATE: {
85+
newSearchParams.set('startDate', action.payload);
86+
url.search = newSearchParams.toString();
87+
window.history.replaceState(null, 'Change URL', url);
8288
return {
8389
...state,
8490
startDate: action.payload,
8591
};
8692
}
8793
case types.UPDATE_END_DATE: {
94+
newSearchParams.set('endDate', action.payload);
95+
url.search = newSearchParams.toString();
96+
window.history.replaceState(null, 'Change URL', url);
8897
return {
8998
...state,
9099
endDate: action.payload,
91100
};
92101
}
93102
case types.UPDATE_REQUEST_TYPES:
103+
if (!state.requestTypes[action.payload]) {
104+
newSearchParams.delete(`rtId${action.payload}`);
105+
} else {
106+
newSearchParams.set(`rtId${action.payload}`, 'false');
107+
}
108+
url.search = newSearchParams.toString();
109+
window.history.replaceState(null, 'Change URL', url);
94110
return {
95111
...state,
96112
requestTypes: {
@@ -99,6 +115,13 @@ export default (state = initialState, action) => {
99115
},
100116
};
101117
case types.UPDATE_NEIGHBORHOOD_COUNCIL:
118+
if (action.payload === state.councilId) {
119+
newSearchParams.delete('councilId');
120+
} else {
121+
newSearchParams.set('councilId', action.payload);
122+
}
123+
url.search = newSearchParams.toString();
124+
window.history.replaceState(null, 'Change URL', url);
102125
return {
103126
...state,
104127
councilId: action.payload,
@@ -116,6 +139,10 @@ export default (state = initialState, action) => {
116139
case types.UPDATE_REQUEST_STATUS:
117140
switch (action.payload) {
118141
case 'all':
142+
newSearchParams.set('requestStatusOpen', true);
143+
newSearchParams.set('requestStatusClosed', true);
144+
url.search = newSearchParams.toString();
145+
window.history.replaceState(null, 'Change URL', url);
119146
return {
120147
...state,
121148
requestStatus: {
@@ -125,6 +152,10 @@ export default (state = initialState, action) => {
125152
},
126153
};
127154
case 'open':
155+
newSearchParams.set('requestStatusOpen', true);
156+
newSearchParams.set('requestStatusClosed', false);
157+
url.search = newSearchParams.toString();
158+
window.history.replaceState(null, 'Change URL', url);
128159
return {
129160
...state,
130161
requestStatus: {
@@ -134,6 +165,10 @@ export default (state = initialState, action) => {
134165
},
135166
};
136167
case 'closed':
168+
newSearchParams.set('requestStatusOpen', false);
169+
newSearchParams.set('requestStatusClosed', true);
170+
url.search = newSearchParams.toString();
171+
window.history.replaceState(null, 'Change URL', url);
137172
return {
138173
...state,
139174
requestStatus: {

0 commit comments

Comments
 (0)