You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
6.1 KiB

  1. package react.map;
  2. import react.ReactComponent;
  3. import react.ReactMacro.jsx;
  4. import Common;
  5. import leaflet.L;
  6. using Lambda;
  7. /**
  8. * Externs for react-leaflet
  9. * @doc https://react-leaflet.js.org/docs/en/intro.html
  10. */
  11. @:jsRequire('react-leaflet', 'Map')
  12. extern class LeafMap extends ReactComponent {}
  13. @:jsRequire('react-leaflet', 'TileLayer')
  14. extern class TileLayer extends ReactComponent {}
  15. @:jsRequire('react-leaflet', 'Marker')
  16. extern class Marker extends ReactComponent {}
  17. @:jsRequire('react-leaflet', 'CircleMarker')
  18. extern class CircleMarker extends ReactComponent {}
  19. @:jsRequire('react-leaflet', 'Popup')
  20. extern class Popup extends ReactComponent {}
  21. @:jsRequire('react-leaflet', 'FeatureGroup')
  22. extern class FeatureGroup extends ReactComponent {}
  23. @:jsRequire('react-leaflet', 'LayerGroup')
  24. extern class LayerGroup extends ReactComponent {}
  25. /*
  26. extern class L2 {
  27. static function icon(a:Dynamic):Dynamic;
  28. static function latLng(lat:Float, lng:Float):Dynamic;
  29. }*/
  30. /**
  31. * GroupItem
  32. * @author rcrestey
  33. */
  34. typedef GroupMapProps = {
  35. var addressCoord:Dynamic;
  36. var groups:Array<GroupOnMap>;
  37. var fetchGroupsInsideBox:Box->Void;
  38. var groupFocusedId:Int;
  39. };
  40. typedef GroupMapState = {
  41. var isFitting:Bool;
  42. var focusedMarker:Dynamic;
  43. };
  44. typedef Box = {
  45. var minLat:Float;
  46. var maxLat:Float;
  47. var minLng:Float;
  48. var maxLng:Float;
  49. };
  50. class GroupMap extends ReactComponentOfPropsAndState<GroupMapProps, GroupMapState> {
  51. static inline var DEFAULT_LAT = 46.52863469527167; // center of France
  52. static inline var DEFAULT_LNG = 2.43896484375; // center of France
  53. static inline var INIT_ZOOM = 6;
  54. static inline var DEFAULT_ZOOM = 13;
  55. var map:Dynamic;
  56. var featureGroup:Dynamic;
  57. var markerMap = new Map<Int,Dynamic>();
  58. var groupIcon = L.icon({
  59. iconUrl: '/img/marker.svg',
  60. iconSize: [40, 40],
  61. iconAnchor: [20, 40],
  62. popupAnchor: [0, -30],
  63. className: 'icon'
  64. });
  65. var homeIcon = L.icon({
  66. iconUrl: '/img/home.svg',
  67. iconSize: [40, 40],
  68. iconAnchor: [20, 20],
  69. popupAnchor: [0, -30],
  70. className: 'icon'
  71. });
  72. function new() {
  73. super();
  74. state = {
  75. isFitting: false,
  76. focusedMarker: null
  77. };
  78. }
  79. function getMap(element:Dynamic):Void {
  80. map = element.leafletElement;
  81. }
  82. function getFeatureGroup(element:Dynamic):Void {
  83. featureGroup = element.leafletElement;
  84. setState({
  85. isFitting: true
  86. }, fitBounds);
  87. }
  88. function getMarker(element:Dynamic, id:Int):Void {
  89. if (element != null)
  90. markerMap.set(id, element.leafletElement);
  91. }
  92. /**
  93. * Call API to get groups in the current bounding box
  94. */
  95. function getGroups() {
  96. var bounds = map.getBounds();
  97. var southWest = bounds.getSouthWest();
  98. var northEast = bounds.getNorthEast();
  99. props.fetchGroupsInsideBox({
  100. minLat: southWest.lat,
  101. maxLat: northEast.lat,
  102. minLng: southWest.lng,
  103. maxLng: northEast.lng
  104. });
  105. }
  106. function fitBounds() {
  107. map.fitBounds(featureGroup.getBounds(), {
  108. padding: [30, 30]
  109. });
  110. }
  111. function handleMoveEnd() {
  112. if (
  113. props.addressCoord != null &&
  114. !Lambda.empty(props.groups) &&
  115. map.distance(map.getCenter(), props.addressCoord) == 0
  116. )
  117. setState({
  118. isFitting: true
  119. }, fitBounds);
  120. else if (state.isFitting)
  121. setState({
  122. isFitting: false
  123. });
  124. else
  125. getGroups();
  126. }
  127. override public function componentDidMount() {
  128. if (props.addressCoord == null)
  129. getGroups();
  130. }
  131. override public function shouldComponentUpdate(nextProps:GroupMapProps, nextState:GroupMapState) {
  132. if (nextState.focusedMarker != state.focusedMarker)
  133. return false;
  134. return true;
  135. }
  136. override public function componentDidUpdate(prevProps:GroupMapProps, prevState:GroupMapState) {
  137. if (props.groupFocusedId != null) {
  138. if (
  139. prevProps.groupFocusedId != props.groupFocusedId
  140. || state.focusedMarker == null
  141. ) {
  142. if (state.focusedMarker != null)
  143. state.focusedMarker.closePopup();
  144. var focusedMarker = markerMap.get(props.groupFocusedId);
  145. focusedMarker.openPopup();
  146. setState({
  147. focusedMarker: focusedMarker
  148. });
  149. }
  150. }
  151. else if (prevProps.groupFocusedId != null && state.focusedMarker != null) {
  152. state.focusedMarker.closePopup();
  153. setState({
  154. focusedMarker: null
  155. });
  156. }
  157. }
  158. override public function render() {
  159. var center = props.addressCoord == null
  160. ? L.latLng(DEFAULT_LAT, DEFAULT_LNG)
  161. : props.addressCoord;
  162. var zoom = props.addressCoord == null
  163. ? INIT_ZOOM
  164. : DEFAULT_ZOOM;
  165. return jsx('
  166. <LeafMap
  167. center=${center}
  168. zoom=${zoom}
  169. ref=${getMap}
  170. onMoveEnd=${handleMoveEnd}
  171. >
  172. <TileLayer
  173. attribution="&amp;copy <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
  174. url="https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYnViYXIiLCJhIjoiY2loM2lubmZpMDBwcGtxbHlwdmw0bXRkbCJ9.rfgXPakoGnXZ3wIGA3-1kQ"
  175. id="bubar.cih3inmqd00tjuxm7oc2532l0"
  176. />
  177. <FeatureGroup ref=${getFeatureGroup}>
  178. ${renderGroupMarkers()}
  179. ${renderHomeMarker()}
  180. </FeatureGroup>
  181. </LeafMap>
  182. ');
  183. }
  184. function renderGroupMarkers() {
  185. var markers = props.groups.map(function(group) {
  186. var coord = [group.place.latitude, group.place.longitude];
  187. function markerGetter(e:Dynamic) {
  188. getMarker(e, group.place.id);
  189. }
  190. var image = group.image==null ? null : jsx('<img className="groupImage img-responsive" src=${group.image}/>');
  191. return jsx('
  192. <Marker
  193. position=${coord}
  194. ref=${markerGetter}
  195. key=${group.place.id}
  196. icon=${groupIcon}
  197. >
  198. <Popup className="popup">
  199. <div>
  200. <a href=${"/group/"+group.id} target="_blank">
  201. $image
  202. <div className="groupName">${group.name}</div>
  203. </a>
  204. </div>
  205. </Popup>
  206. </Marker>
  207. ');
  208. });
  209. return jsx('<div>${markers}</div>');
  210. }
  211. function renderHomeMarker() {
  212. if (props.addressCoord != null)
  213. return jsx('<Marker position=${props.addressCoord} icon=${homeIcon} />');
  214. return null;
  215. }
  216. }