website/src/Components/Map.js
2020-03-07 22:16:25 +01:00

261 lines
6.8 KiB
JavaScript

import React from 'react';
import Axios from "axios";
import ReactMapGL, { Marker } from 'react-map-gl';
import { Button } from "react-bootstrap";
import { withToastManager } from 'react-toast-notifications';
import PropTypes from 'prop-types';
import { MdLocalGasStation } from "react-icons/md";
import { GiPositionMarker } from "react-icons/gi";
import { haveSelectedGas } from '../helpers';
import { mapboxToken, radius, baseApiUrl } from "../config";
import 'mapbox-gl/dist/mapbox-gl.css';
class Map extends React.Component {
constructor(props) {
super(props);
this.state = {
viewport: {
width: '100vw',
height: '100vh',
latitude: 44.837789,
longitude: -0.57918,
zoom: 11,
},
userLocation: {},
gasStations: [],
};
this.currentLoadId = null;
this.isFirstLoading = true;
this.mapRef = React.createRef();
this.loadGasStations();
}
/**
* Méthode appelée au premier chargement du module
*/
componentDidMount() {
this.setUserLocation();
}
/**
* Méthode appelée à chaque modificiation de l'état
* @param {Object} prevProps
*/
componentDidUpdate( prevProps ) {
const {
setNeedUpdateUserLocation,
} = this.props;
// L'utilisateur souhaite que l'app le géoloc
if ( prevProps.needUpdateUserLocation ) {
setNeedUpdateUserLocation(false);
this.setUserLocation(() => {
this.loadGasStations();
});
}
}
/**
* Méthode permettant de mettre à jour la position de la map
* @param {Object} viewport
*/
onViewportChange = (viewport) => {
this.setState({ viewport }, () => {
this.loadGasStations();
});
}
/**
* Méthode permettant de sauvegarder la position de l'utilisateur
* @param {Function} [cb]
*/
setUserLocation(cb) {
navigator.geolocation.getCurrentPosition((position) => {
const setUserLocation = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
const newViewport = {
height: '100vh',
width: '100vw',
latitude: position.coords.latitude,
longitude: position.coords.longitude,
zoom: 11,
};
this.setState({
viewport: newViewport,
userLocation: setUserLocation,
}, () => {
if ( cb ) {
cb();
}
});
});
}
/**
* Méthode récursive permettant de charger tous les stations d'une zone
* @param {Number} start
* @param {Number} newLoadId
* @param {Function} callback
*/
loadAllGasStations(start, newLoadId, callback) {
const limit = 20;
const {
viewport,
} = this.state;
const {
latitude,
longitude
} = viewport;
if ( newLoadId !== this.currentLoadId ) {
callback(new Error('Request canceled'))
} else {
Axios.get(`${baseApiUrl}stations?loadId=${newLoadId}&radius=${radius}&lat=${latitude}&lon=${longitude}&start=${start}&limit=${limit}`)
.then( (response) => {
if ( response.status === 200 ){
const newStations = response.data.items;
let stations = newStations;
const nextStart = start + limit;
if ( nextStart > response.data.total ) {
callback( null, stations )
} else {
this.loadAllGasStations(nextStart, newLoadId, (err, otherStations) => {
if ( err ) {
callback(err);
} else {
stations = stations.concat(otherStations)
callback(null, stations);
}
})
}
} else {
callback(null, []);
}
})
.catch( (err) => {
callback(err);
})
}
}
/**
* Méthode permettant de charger le fichier xml contenant la liste des stations
*/
loadGasStations() {
const newLoadId = new Date().getTime();
const {
toastManager,
} = this.props;
if ( !this.currentLoadId ){
if ( this.isFirstLoading ) {
toastManager.add('Chargement des données...', { appearance: 'info', autoDismiss: true });
} else {
toastManager.add('Actualisation des données...', { appearance: 'info', autoDismiss: true });
}
}
this.currentLoadId = newLoadId;
this.loadAllGasStations(0, newLoadId, (err,gasStations) => {
this.isLoading = false;
if ( this.currentLoadId === newLoadId ) {
toastManager.removeAll();
if ( err ) {
toastManager.add('Erreur lors du chargement des données', { appearance: 'error', autoDismiss: true });
} else {
toastManager.add('Chargement des données terminé', { appearance: 'success', autoDismiss: true });
this.currentLoadId = null;
this.isFirstLoading = false;
this.setState((prevState) => ({
...prevState,
gasStations,
}));
}
}
})
}
/**
* Méthode gérant le rendu de la vue
*/
render() {
const {
viewport,
userLocation,
gasStations,
} = this.state;
const {
showGasStation,
selectedGasType,
} = this.props;
return (
<>
<ReactMapGL
{...viewport}
mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={this.onViewportChange}
mapboxApiAccessToken={mapboxToken}
ref={(map) => { if (map) this.mapRef = map; }}
>
{
Object.keys(userLocation).length !== 0 ? (
<Marker
latitude={userLocation.latitude}
longitude={userLocation.longitude}
>
<GiPositionMarker size="2em" />
</Marker>
) : (null)
}
{gasStations.filter((station) => haveSelectedGas(station, selectedGasType)).map((gasStation) => (
<Marker
key={gasStation.stationId}
latitude={gasStation.location.coordinates[1]}
longitude={gasStation.location.coordinates[0]}
className="gasStation"
>
<Button
variant="link"
onClick={() => showGasStation(gasStation)}
onFocus={() => { }}
onBlur={() => { }}
>
<MdLocalGasStation size="2em" />
</Button>
</Marker>
))}
</ReactMapGL>
</>
);
}
}
Map.defaultProps = {
needUpdateUserLocation: false
};
Map.propTypes = {
needUpdateUserLocation: PropTypes.bool,
selectedGasType: PropTypes.string.isRequired,
showGasStation: PropTypes.func.isRequired,
setNeedUpdateUserLocation: PropTypes.func.isRequired,
toastManager: PropTypes.shape({
add: PropTypes.func,
removeAll: PropTypes.func,
}).isRequired,
};
export default withToastManager(Map);