code from amapei

This commit is contained in:
cagette@ct8
2020-09-26 18:25:18 +00:00
parent 42fe367911
commit 72b4699871
1966 changed files with 220437 additions and 2 deletions
+252
View File
@@ -0,0 +1,252 @@
package react.map;
import react.ReactComponent;
import react.ReactMacro.jsx;
import Common;
import leaflet.L;
using Lambda;
/**
* Externs for react-leaflet
* @doc https://react-leaflet.js.org/docs/en/intro.html
*/
@:jsRequire('react-leaflet', 'Map')
extern class LeafMap extends ReactComponent {}
@:jsRequire('react-leaflet', 'TileLayer')
extern class TileLayer extends ReactComponent {}
@:jsRequire('react-leaflet', 'Marker')
extern class Marker extends ReactComponent {}
@:jsRequire('react-leaflet', 'CircleMarker')
extern class CircleMarker extends ReactComponent {}
@:jsRequire('react-leaflet', 'Popup')
extern class Popup extends ReactComponent {}
@:jsRequire('react-leaflet', 'FeatureGroup')
extern class FeatureGroup extends ReactComponent {}
@:jsRequire('react-leaflet', 'LayerGroup')
extern class LayerGroup extends ReactComponent {}
/*
extern class L2 {
static function icon(a:Dynamic):Dynamic;
static function latLng(lat:Float, lng:Float):Dynamic;
}*/
/**
* GroupItem
* @author rcrestey
*/
typedef GroupMapProps = {
var addressCoord:Dynamic;
var groups:Array<GroupOnMap>;
var fetchGroupsInsideBox:Box->Void;
var groupFocusedId:Int;
};
typedef GroupMapState = {
var isFitting:Bool;
var focusedMarker:Dynamic;
};
typedef Box = {
var minLat:Float;
var maxLat:Float;
var minLng:Float;
var maxLng:Float;
};
class GroupMap extends ReactComponentOfPropsAndState<GroupMapProps, GroupMapState> {
static inline var DEFAULT_LAT = 46.52863469527167; // center of France
static inline var DEFAULT_LNG = 2.43896484375; // center of France
static inline var INIT_ZOOM = 6;
static inline var DEFAULT_ZOOM = 13;
var map:Dynamic;
var featureGroup:Dynamic;
var markerMap = new Map<Int,Dynamic>();
var groupIcon = L.icon({
iconUrl: '/img/marker.svg',
iconSize: [40, 40],
iconAnchor: [20, 40],
popupAnchor: [0, -30],
className: 'icon'
});
var homeIcon = L.icon({
iconUrl: '/img/home.svg',
iconSize: [40, 40],
iconAnchor: [20, 20],
popupAnchor: [0, -30],
className: 'icon'
});
function new() {
super();
state = {
isFitting: false,
focusedMarker: null
};
}
function getMap(element:Dynamic):Void {
map = element.leafletElement;
}
function getFeatureGroup(element:Dynamic):Void {
featureGroup = element.leafletElement;
setState({
isFitting: true
}, fitBounds);
}
function getMarker(element:Dynamic, id:Int):Void {
if (element != null)
markerMap.set(id, element.leafletElement);
}
/**
* Call API to get groups in the current bounding box
*/
function getGroups() {
var bounds = map.getBounds();
var southWest = bounds.getSouthWest();
var northEast = bounds.getNorthEast();
props.fetchGroupsInsideBox({
minLat: southWest.lat,
maxLat: northEast.lat,
minLng: southWest.lng,
maxLng: northEast.lng
});
}
function fitBounds() {
map.fitBounds(featureGroup.getBounds(), {
padding: [30, 30]
});
}
function handleMoveEnd() {
if (
props.addressCoord != null &&
!Lambda.empty(props.groups) &&
map.distance(map.getCenter(), props.addressCoord) == 0
)
setState({
isFitting: true
}, fitBounds);
else if (state.isFitting)
setState({
isFitting: false
});
else
getGroups();
}
override public function componentDidMount() {
if (props.addressCoord == null)
getGroups();
}
override public function shouldComponentUpdate(nextProps:GroupMapProps, nextState:GroupMapState) {
if (nextState.focusedMarker != state.focusedMarker)
return false;
return true;
}
override public function componentDidUpdate(prevProps:GroupMapProps, prevState:GroupMapState) {
if (props.groupFocusedId != null) {
if (
prevProps.groupFocusedId != props.groupFocusedId
|| state.focusedMarker == null
) {
if (state.focusedMarker != null)
state.focusedMarker.closePopup();
var focusedMarker = markerMap.get(props.groupFocusedId);
focusedMarker.openPopup();
setState({
focusedMarker: focusedMarker
});
}
}
else if (prevProps.groupFocusedId != null && state.focusedMarker != null) {
state.focusedMarker.closePopup();
setState({
focusedMarker: null
});
}
}
override public function render() {
var center = props.addressCoord == null
? L.latLng(DEFAULT_LAT, DEFAULT_LNG)
: props.addressCoord;
var zoom = props.addressCoord == null
? INIT_ZOOM
: DEFAULT_ZOOM;
return jsx('
<LeafMap
center=${center}
zoom=${zoom}
ref=${getMap}
onMoveEnd=${handleMoveEnd}
>
<TileLayer
attribution="&amp;copy <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
url="https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYnViYXIiLCJhIjoiY2loM2lubmZpMDBwcGtxbHlwdmw0bXRkbCJ9.rfgXPakoGnXZ3wIGA3-1kQ"
id="bubar.cih3inmqd00tjuxm7oc2532l0"
/>
<FeatureGroup ref=${getFeatureGroup}>
${renderGroupMarkers()}
${renderHomeMarker()}
</FeatureGroup>
</LeafMap>
');
}
function renderGroupMarkers() {
var markers = props.groups.map(function(group) {
var coord = [group.place.latitude, group.place.longitude];
function markerGetter(e:Dynamic) {
getMarker(e, group.place.id);
}
var image = group.image==null ? null : jsx('<img className="groupImage img-responsive" src=${group.image}/>');
return jsx('
<Marker
position=${coord}
ref=${markerGetter}
key=${group.place.id}
icon=${groupIcon}
>
<Popup className="popup">
<div>
<a href=${"/group/"+group.id} target="_blank">
$image
<div className="groupName">${group.name}</div>
</a>
</div>
</Popup>
</Marker>
');
});
return jsx('<div>${markers}</div>');
}
function renderHomeMarker() {
if (props.addressCoord != null)
return jsx('<Marker position=${props.addressCoord} icon=${homeIcon} />');
return null;
}
}
+312
View File
@@ -0,0 +1,312 @@
package react.map;
import js.Promise;
import react.ReactComponent;
import react.ReactMacro.jsx;
import utils.HttpUtil;
import leaflet.L;
import Common;
using Lambda;
@:jsRequire('react-places-autocomplete', 'default')
extern class Autocomplete extends ReactComponent {}
@:jsRequire('react-places-autocomplete')
extern class GeoUtil {
static function geocodeByAddress(address:Dynamic):Promise<Dynamic>;
}
@:jsRequire('geolib')
extern class Geolib {
static function getDistance(start:Dynamic, end:Dynamic):Float;
}
/**
* Groups Map
* @author rcrestey
*/
typedef GroupMapRootState = {
var point:Dynamic;
var address:String;
var groups:Array<GroupOnMap>;
var groupFocusedId:Int;
var isInit:Bool;
};
typedef GroupMapRootProps = {
var lat:Float;
var lng:Float;
var address:String;
};
class GroupMapRoot extends ReactComponentOfState<GroupMapRootState>{
static inline var GROUP_MAP_URL = '/api/group/map';
var distanceMap = new Map<Int,Dynamic>();
public function new(props)
{
super(props);
state = {
point: L.latLng(props.lat, props.lng),
address: props.address,
groups: [],
groupFocusedId: null,
isInit: false
};
}
function onChange(address) {
setState({
address: address
});
}
function openPopup(group:Dynamic) {
setState({
groupFocusedId: group.place.id
});
}
function closePopup() {
setState({
groupFocusedId: null
});
}
function geocodeByAddress(address:String):Promise<Dynamic> {
return GeoUtil.geocodeByAddress(address)
.then(function(results) {
var lat = results[0].geometry.location.lat();
var lng = results[0].geometry.location.lng();
return {lat: lat, lng: lng};
});
}
/**
* Call API to find groups at $lat and $lng
* @param lat -
* @param lng -
*/
function fetchGroups(lat:Float, lng:Float) {
HttpUtil.fetch(GROUP_MAP_URL, GET, {lat: lat, lng: lng}, JSON)
.then(function(results) {
setState({
point: L.latLng(lat, lng),
groups: results.groups,
isInit: true
}, fillDistanceMap);
})
.catchError(function(error) {
trace('Error', error);
});
}
/**
* Call API to look for groups in the defined bounding box
*/
var wait : Bool;
function fetchGroupsInsideBox(newBox) {
switch(wait){
case null,false : wait = true;
case true : trace("stop"); return;
}
HttpUtil.fetch(GROUP_MAP_URL, GET, newBox, JSON)
.then(function(results) {
wait = false;
setState({
groups: results.groups
}, fillDistanceMap);
});
/*.catchError(function(error) {
trace('Error', error + " stack:"+haxe.CallStack.toString(haxe.CallStack.exceptionStack())) ;
wait = false;
});*/
}
function getGroupDistance(group:GroupOnMap):Float {
if (state.point == null)
return null;
var start = {
latitude: state.point.lat,
longitude: state.point.lng
};
var end = {
latitude: group.place.latitude,
longitude: group.place.longitude
};
return Geolib.getDistance(start, end);
}
function fillDistanceMap() {
for (group in state.groups) {
distanceMap.set(group.place.id, getGroupDistance(group));
}
orderGroupsByDistance(state.groups);
setState({
groups: state.groups
});
}
function orderGroupsByDistance(groups:Array<GroupOnMap>) {
groups.sort(function(a, b) {
return distanceMap.get(a.place.id) - distanceMap.get(b.place.id);
});
}
function convertDistance(distance:Int):String { // to test
if (distance > 10000)
return Math.floor(distance / 1000) + ' km';
if (distance > 1000)
return Math.floor(distance / 100) / 10 + ' km';
return distance + ' m';
}
function handleSelect(address:String) {
setState({
address: address
});
geocodeByAddress(address)
.then(function(coord) {
fetchGroups(coord.lat, coord.lng);
})
.catchError(function(error) {
trace('Error', error);
});
}
override public function componentDidMount() {
if (state.point != null)
fetchGroups(state.point.lat, state.point.lng);
else if (state.address != '')
handleSelect(state.address);
}
function renderSuggestion(obj:Dynamic) {
return jsx('
<div className="autocomplete-item">
<i className="fa fa-map-marker autocomplete-icon" />
<strong>${obj.formattedSuggestion.mainText}</strong>&nbsp;
<small className="text-muted">${obj.formattedSuggestion.secondaryText}</small>
</div>
');
}
override public function render() {
var inputProps = {
value: state.address,
onChange: onChange
};
var cssClasses = {
root: 'form-group',
input: 'autocomplete-input',
autocompleteContainer: 'autocomplete-results',
};
return jsx('
<div className="group-map">
<div className="row">
<div id="logo" className="col-md-3">&nbsp;</div>
<div className="col-md-9">
<div className="form-group-container">
Trouvez un groupe Cagette près de chez vous &nbsp;
<Autocomplete
inputProps=${inputProps}
onSelect=${handleSelect}
classNames=${cssClasses}
renderSuggestion=${renderSuggestion}
placeHolder="Saisissez votre adresse"
/>
</div>
</div>
</div>
<div className="row">
<div className="col-md-3" id="groupsContainer">${renderGroupList()}</div>
<div className="col-md-9" id="mapContainer">${renderGroupMap()}</div>
</div>
</div>');
}
function renderGroupMap() {
if (!state.isInit)
return null;
return jsx('
<GroupMap
addressCoord=${state.point}
groups=${state.groups}
fetchGroupsInsideBox=${fetchGroupsInsideBox}
groupFocusedId=${state.groupFocusedId}
/>
');
}
function renderGroupList() {
var groups = state.groups.map(function(group) {
return renderGroup(group);
});
return jsx('
<div className="groups">
${groups}
</div>
');
}
/**
* Renders a group in the left list
*/
function renderGroup(group:GroupOnMap) {
var address = [
group.place.address1,
group.place.address2,
[group.place.zipCode, group.place.city].join(" "),
];
var addressBlock = Lambda.array(address.mapi(function(index, element) {
if (element != null){
return jsx('<div key=${index}>$element</div>');
}else{
return null;
}
}));
var distance = null;
if (distanceMap.get(group.place.id) != null)
distance = jsx('<div className="distance">${convertDistance(distanceMap.get(group.place.id))}</div>');
var classNames = ['clickable groupBlock'];
if (group.place.id == state.groupFocusedId)
classNames.push('focused');
var img = if(group.image==null) {
null;
}else{
jsx('<img src="${group.image}" className="img-responsive" />');
}
return jsx('<a target="_blank"
onMouseEnter=${function() { openPopup(group); }}
onMouseLeave=${closePopup}
className=${classNames.join(' ')}
key=${group.place.id}
href=${"/group/"+group.id}
>
$img
<h4>${group.name}</h4>
<div className="address">${addressBlock}</div>
${distance}
</a>');
}
}