Skip to content

Commit 48c8f18

Browse files
author
gondzo
committed
Merge branch 'plannerUpdates' into dev
# Conflicts: # .env # src/components/Pagination/Pagination.scss # src/config/index.js # src/routes/MissionPlanner/components/MissionMap/MissionMap.jsx
2 parents 38bff99 + 32a7b32 commit 48c8f18

File tree

15 files changed

+325
-52
lines changed

15 files changed

+325
-52
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"react-table": "^3.1.4",
100100
"react-tabs": "^0.8.2",
101101
"react-timeago": "^3.1.3",
102+
"react-toggle-button": "^2.1.0",
102103
"reactable": "^0.14.1",
103104
"redbox-react": "^1.2.10",
104105
"redux": "^3.0.0",

src/components/Pagination/Pagination.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
.next.disabled,
6161
.prev.disabled {
62-
background-color: rgba(#efefef, 0.5);
62+
background-color: transparent;
6363

6464
> a {
6565
outline: none;

src/config/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ module.exports = {
1313
AUTH0_CLIENT_DOMAIN: process.env.REACT_APP_AUTH0_CLIENT_DOMAIN || 'spanhawk.auth0.com',
1414
AUTH0_CALLBACK: 'http://localhost:3000',
1515
CLOUDINARY_ACCOUNT_NAME: process.env.CLOUDINARY_ACCOUNT_NAME || 'dsp',
16+
REGION_TYPES: {
17+
POINT: 'Point',
18+
POLYGON: 'Polygon',
19+
},
20+
USER_LOCATION_KEY: 'ul',
1621
};

src/layouts/CoreLayout/CoreLayout.jsx

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,85 @@
11
import React, {PropTypes} from 'react';
22
import CSSModules from 'react-css-modules';
3+
import {connect} from 'react-redux';
34
import HeaderContainer from 'containers/HeaderContainer';
45
import Breadcrumbs from 'react-breadcrumbs';
56
import Footer from 'components/Footer';
67
import styles from './CoreLayout.scss';
8+
import {userLocationUpdateAction} from '../../store/modules/global';
9+
import _ from 'lodash';
10+
import config from '../../config';
711

8-
export const CoreLayout = ({children, routes, params}) => (
9-
<div styleName="core-layout">
10-
<HeaderContainer routes={routes} />
12+
class CoreLayout extends React.Component {
1113

12-
{ (children.props.route.path !== 'home' && children.props.route.path !== 'browse-provider') &&
13-
<div className="breadcrumb-container">
14-
<Breadcrumbs routes={routes} params={params} excludes={['CoreLayout', 'ServiceRequest']} />
15-
</div> }
14+
constructor(props) {
15+
super(props);
16+
this.requestUserLocation = this.requestUserLocation.bind(this);
17+
}
18+
/**
19+
* React lifecycle method which is invoked after this component is mounted
20+
* This is invoked only on the page reload
21+
*/
22+
componentDidMount() {
23+
// component did mount will be called on page reload, so if already a location is cached use that
24+
// and if not than request location from user
25+
this.requestUserLocation();
26+
}
1627

28+
/**
29+
* Request a user location and fire redux action handler
30+
*/
31+
requestUserLocation() {
32+
const {onUserLocationUpdate} = this.props;
33+
// don't request the permission everytime and use caching
34+
const cachedLocation = localStorage.getItem(config.USER_LOCATION_KEY);
35+
// just to be extra safe here as a user can manipulate the content of local storage
36+
if (cachedLocation && _.has(cachedLocation, 'lat') && _.has(cachedLocation, 'lng')) {
37+
onUserLocationUpdate(cachedLocation);
38+
} else if (_.hasIn(navigator, 'geolocation.getCurrentPosition')) {
39+
// request user location
40+
navigator.geolocation.getCurrentPosition((pos) => {
41+
onUserLocationUpdate({lat: pos.coords.latitude, lng: pos.coords.longitude});
42+
},
43+
null,
44+
{timeout: 60000}
45+
);
46+
}
47+
}
1748

18-
<div styleName="content">
19-
{children}
20-
</div>
21-
<Footer />
22-
</div>
23-
);
49+
render() {
50+
const {children, routes, params} = this.props;
51+
return (
52+
<div styleName="core-layout">
53+
<HeaderContainer routes={routes} />
54+
55+
{ (children.props.route.path !== 'home' && children.props.route.path !== 'browse-provider') &&
56+
<div className="breadcrumb-container">
57+
<Breadcrumbs routes={routes} params={params} excludes={['CoreLayout', 'ServiceRequest']} />
58+
</div> }
59+
60+
61+
<div styleName="content">
62+
{children}
63+
</div>
64+
<Footer />
65+
</div>
66+
);
67+
}
68+
}
2469

2570
CoreLayout.propTypes = {
2671
children: PropTypes.any.isRequired,
2772
routes: PropTypes.any.isRequired,
2873
params: PropTypes.any.isRequired,
74+
onUserLocationUpdate: PropTypes.func.isRequired,
2975
};
3076

31-
export default CSSModules(CoreLayout, styles);
77+
const mapState = (state) => state.global;
78+
79+
const mapDispatchToProps = (dispatch) => ({
80+
onUserLocationUpdate: (location) => {
81+
dispatch(userLocationUpdateAction(location));
82+
},
83+
});
84+
85+
export default connect(mapState, mapDispatchToProps)(CSSModules(CoreLayout, styles));

src/routes/MissionPlanner/components/MissionMap/MissionMap.jsx

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import CSSModules from 'react-css-modules';
33
import {withGoogleMap, GoogleMap, Marker, Polyline} from 'react-google-maps';
44
import _ from 'lodash';
55
import NoFlyZone from 'components/NoFlyZone';
6+
import Rtfz from '../Rtfz';
67
import {GOOGLE_MAPS_BOUNDS_TIMEOUT} from 'Const';
78
import styles from './MissionMap.scss';
9+
import config from '../../../../config';
810

911
// default center location for mission Planner
1012
const mapConfig = {
@@ -32,19 +34,27 @@ export const MissionGoogleMap = withGoogleMap((props) => (
3234
{... mapConfig}
3335
onBoundsChanged={props.onBoundsChanged}
3436
ref={props.onMapLoad}
37+
options={{
38+
mapTypeControl: true,
39+
mapTypeControlOptions: {
40+
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
41+
position: google.maps.ControlPosition.TOP_CENTER,
42+
},
43+
}}
3544
onClick={props.onMapClick}
3645
>
3746
{props.markers.map((marker, index) => (
3847
<Marker key={index} {...marker} onDrag={(event) => props.onMarkerDrag(event, index)} />
3948
))}
40-
<Polyline {...polylineConfig} path={props.lineMarkerPosistions} />
49+
<Polyline {...polylineConfig} path={props.lineMarkerPositions} />
4150
{props.noFlyZones.map((zone) => <NoFlyZone key={zone.id} zone={zone} />)}
51+
{props.rtfzs && props.rtfzs.filter((single) => single.show === true).map((rtfz) => <Rtfz key={rtfz._id} zone={rtfz} />)}
4252
</GoogleMap>
4353
));
4454

4555
MissionGoogleMap.propTypes = {
4656
markers: PropTypes.array,
47-
lineMarkerPosistions: PropTypes.array,
57+
lineMarkerPositions: PropTypes.array,
4858
onMapLoad: PropTypes.func,
4959
onMapClick: PropTypes.func,
5060
onMarkerDrag: PropTypes.func,
@@ -62,43 +72,78 @@ export class MissionMap extends Component {
6272
this.handleMapLoad = this.handleMapLoad.bind(this);
6373

6474
this.state = {
65-
lineMarkerPosistions: getLineMarkerPositions(props.markers),
75+
lineMarkerPositions: getLineMarkerPositions(props.markers),
6676
};
6777
}
6878

6979
componentWillReceiveProps(nextProps) {
7080
this.setState({
71-
lineMarkerPosistions: getLineMarkerPositions(nextProps.markers),
81+
lineMarkerPositions: getLineMarkerPositions(nextProps.markers),
7282
});
83+
// only required if the user location is updated
84+
// because bounds for markers and rtfzs are already set in handleMapLoad
85+
const shouldUpdateBound = !_.isEqual(this.props.userLocation, nextProps.userLocation);
86+
if (shouldUpdateBound) {
87+
const {markers, rtfzs, userLocation} = nextProps;
88+
const bounds = this.getMapBounds(markers, rtfzs, userLocation);
89+
this.map.fitBounds(bounds);
90+
}
7391
}
7492

75-
fitMapToBounds(map, markers) {
76-
if (markers.length) {
77-
const markersBounds = new google.maps.LatLngBounds();
78-
79-
for (const marker of this.props.markers) {
80-
markersBounds.extend(marker.position);
81-
}
82-
83-
map.fitBounds(markersBounds);
93+
/**
94+
* Intelligently determine the map bounds to fit the map
95+
* The order of precedence
96+
* 1. If markers are defined return the bounds for markers
97+
* 2. If markers are undefined and rtfzs are defined than return the bounds for rtfzs
98+
* 3. If mission items and rtfzs are undefined than return the bounds for current user location
99+
* if user denied location than return the default bounds
100+
* 4. If markers and rtfzs are defined return bounds for markers
101+
*
102+
* @param {Array} markers the list of markers to get the bounds
103+
* @param {Array} rtfzs the list of rtfzs to get the bounds
104+
* @param {Object} userLocation the user location to get the bounds
105+
*/
106+
getMapBounds(markers, rtfzs, userLocation) {
107+
const isMarkers = markers && markers.length > 0;
108+
const isRtfzs = rtfzs && rtfzs.length > 0;
109+
const isUserLocation = userLocation && _.has(userLocation, 'lat') && _.has(userLocation, 'lng');
110+
let bounds;
111+
if (isMarkers) {
112+
bounds = new google.maps.LatLngBounds();
113+
// bounds for markers
114+
markers.forEach((marker) => {
115+
bounds.extend(marker.position);
116+
});
117+
} else if (!isMarkers && isRtfzs) {
118+
bounds = new google.maps.LatLngBounds();
119+
// bounds for rtfzs
120+
rtfzs.forEach((rtfz) => {
121+
if (rtfz.location.type === config.REGION_TYPES.POINT) {
122+
bounds.extend({lat: rtfz.location.coordinates[1], lng: rtfz.location.coordinates[0]});
123+
} else if (rtfz.location.type === config.REGION_TYPES.POLYGON) {
124+
rtfz.location.coordinates.forEach((coor) => {
125+
coor.forEach((point) => {
126+
bounds.extend({lat: point[1], lng: point[0]});
127+
});
128+
});
129+
}
130+
});
131+
} else if (!isMarkers && !isRtfzs && isUserLocation) {
132+
bounds = new google.maps.LatLngBounds();
133+
// bounds for user location
134+
bounds.extend(userLocation);
84135
}
136+
return bounds;
85137
}
86138

87139
handleMapLoad(map) {
140+
const {markers, rtfzs, userLocation} = this.props;
88141
this.map = map;
89142
if (map) {
90-
if (this.props.markers.length > 0) {
91-
this.fitMapToBounds(map, this.props.markers);
92-
} else {
93-
navigator.geolocation.getCurrentPosition((pos) => {
94-
map.panTo({
95-
lat: pos.coords.latitude,
96-
lng: pos.coords.longitude,
97-
});
98-
},
99-
null,
100-
{timeout: 60000}
101-
);
143+
const bounds = this.getMapBounds(markers, rtfzs, userLocation);
144+
// if bounds are defined than only fit map to bounds, otherwise keep default bounds
145+
if (bounds) {
146+
map.fitBounds(bounds);
102147
}
103148
}
104149
}
@@ -125,14 +170,15 @@ export class MissionMap extends Component {
125170
this.setState((prevState) => {
126171
const newState = _.cloneDeep(prevState);
127172

128-
newState.lineMarkerPosistions[index - 1] = event.latLng;
173+
newState.lineMarkerPositions[index - 1] = event.latLng;
129174

130175
return newState;
131176
});
132177
}
133178
}}
134-
lineMarkerPosistions={this.state.lineMarkerPosistions}
179+
lineMarkerPositions={this.state.lineMarkerPositions}
135180
noFlyZones={this.props.noFlyZones}
181+
rtfzs={this.props.rtfzs}
136182
/>
137183
</div>
138184
);
@@ -144,6 +190,9 @@ MissionMap.propTypes = {
144190
onMapClick: PropTypes.func,
145191
loadNfz: PropTypes.func.isRequired,
146192
noFlyZones: PropTypes.array.isRequired,
193+
rtfzs: PropTypes.array,
194+
// the current cached user location
195+
userLocation: PropTypes.object,
147196
};
148197

149198
export default CSSModules(MissionMap, styles);

src/routes/MissionPlanner/components/MissionPlannerView.jsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, {PropTypes} from 'react';
22
import CSSModules from 'react-css-modules';
33
import MissionMap from './MissionMap';
44
import MissionSidebar from './MissionSidebar';
5+
import RTFZSidebar from './RTFZSidebar';
56
import MissionPlannerHeader from '../containers/MissionPlannerHeaderContainer';
67
import styles from './MissionPlannerView.scss';
78

@@ -12,9 +13,12 @@ const waypointIcon = getImage('icon-waypoint-blue.png');
1213

1314
export const getMissionItemsExt = (mission) => {
1415
let missionItemsExt = [];
15-
1616
mission.plannedHomePosition && missionItemsExt.push(mission.plannedHomePosition);
17-
missionItemsExt = [...missionItemsExt, ...mission.missionItems];
17+
18+
19+
if (mission.missionItems) {
20+
missionItemsExt = [...missionItemsExt, ...mission.missionItems];
21+
}
1822

1923
return missionItemsExt;
2024
};
@@ -33,7 +37,6 @@ export const getMarkerProps = (item, updateMissionItem) => {
3337
item.coordinate[2],
3438
],
3539
};
36-
3740
updateMissionItem(item.id, newMissionItem);
3841
},
3942
};
@@ -64,7 +67,7 @@ export const getMarkerProps = (item, updateMissionItem) => {
6467
return markerProps;
6568
};
6669

67-
export const MissionPlannerView = ({mission, updateMissionItem, addMissionItem, deleteMissionItem, loadNfz, noFlyZones}) => {
70+
export const MissionPlannerView = ({mission, toggleRtfzHandler, userLocation, updateMissionItem, addMissionItem, deleteMissionItem, loadNfz, noFlyZones}) => {
6871
const missionItemsExt = getMissionItemsExt(mission);
6972
const filteredMissionItemsExt = missionItemsExt.filter((item) => (item.command !== 203));
7073
const markersExt = filteredMissionItemsExt.map((item) => getMarkerProps(item, updateMissionItem));
@@ -79,9 +82,12 @@ export const MissionPlannerView = ({mission, updateMissionItem, addMissionItem,
7982
loadNfz={loadNfz}
8083
noFlyZones={noFlyZones}
8184
markers={markersExt}
85+
rtfzs={mission.zones}
86+
userLocation={userLocation}
8287
onMapClick={(event) => addMissionItem({lat: event.latLng.lat(), lng: event.latLng.lng()})}
8388
/>
8489
<MissionSidebar missionItems={missionItemsExt} onUpdate={updateMissionItem} onDelete={deleteMissionItem} />
90+
<RTFZSidebar rtfzs={mission.zones} toggleRtfzHandler={toggleRtfzHandler} />
8591
</div>
8692
</div>
8793
);
@@ -94,6 +100,9 @@ MissionPlannerView.propTypes = {
94100
deleteMissionItem: PropTypes.func.isRequired,
95101
loadNfz: PropTypes.func.isRequired,
96102
noFlyZones: PropTypes.array.isRequired,
103+
toggleRtfzHandler: PropTypes.func.isRequired,
104+
// the current cached user location
105+
userLocation: PropTypes.object,
97106
};
98107

99108
export default CSSModules(MissionPlannerView, styles);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy