From 935d11995483127df059a03f8befdce664a8ebee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Co=C5=9Fkun?= Date: Thu, 11 Jun 2020 12:45:05 +0300 Subject: [PATCH 1/3] nci-agency/anet#3045: Add all locations overlay to the map --- client/src/components/Leaflet.js | 89 +++++++++++++++++- client/src/index.css | 33 +++++++ client/src/pages/App.js | 17 +++- client/src/pages/locations/Form.js | 4 +- .../resources/leaflet/marker-flag-blue-2x.png | Bin 0 -> 1669 bytes .../resources/leaflet/marker-flag-blue.png | Bin 0 -> 887 bytes 6 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 client/src/resources/leaflet/marker-flag-blue-2x.png create mode 100644 client/src/resources/leaflet/marker-flag-blue.png diff --git a/client/src/components/Leaflet.js b/client/src/components/Leaflet.js index 5e4c23c802..0417a5cc0e 100644 --- a/client/src/components/Leaflet.js +++ b/client/src/components/Leaflet.js @@ -1,4 +1,6 @@ -import { Control, CRS, Icon, Map, Marker, TileLayer } from "leaflet" +import AppContext from "components/AppContext" +import GeoLocation from "components/GeoLocation" +import { Control, CRS, DivIcon, Icon, Map, Marker, TileLayer } from "leaflet" import "leaflet-defaulticon-compatibility" import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css" import { @@ -16,6 +18,9 @@ import "leaflet/dist/leaflet.css" import { Location } from "models" import PropTypes from "prop-types" import React, { useCallback, useEffect, useRef, useState } from "react" +import ReactDOM from "react-dom" +import MARKER_FLAG_BLUE_2X from "resources/leaflet/marker-flag-blue-2x.png" +import MARKER_FLAG_BLUE from "resources/leaflet/marker-flag-blue.png" import MARKER_ICON_2X from "resources/leaflet/marker-icon-2x.png" import MARKER_ICON from "resources/leaflet/marker-icon.png" import MARKER_SHADOW from "resources/leaflet/marker-shadow.png" @@ -64,6 +69,14 @@ const icon = new Icon({ shadowSize: [41, 41] }) +const locationIcon = new Icon({ + iconUrl: MARKER_FLAG_BLUE, + iconRetinaUrl: MARKER_FLAG_BLUE_2X, + iconSize: [64, 64], + iconAnchor: [18, 62], + popupAnchor: [2, -58] +}) + const addLayers = (map, layerControl) => { let defaultLayer = null Settings.imagery.baseLayers.forEach(layerConfig => { @@ -85,11 +98,12 @@ const addLayers = (map, layerControl) => { } } -const Leaflet = ({ +const BaseLeaflet = ({ width, height, marginBottom, markers, + allLocations, mapId: initialMapId, onMapClick }) => { @@ -108,6 +122,7 @@ const Leaflet = ({ const [map, setMap] = useState(null) const [markerLayer, setMarkerLayer] = useState(null) + const [layerControl, setLayerControl] = useState(null) const [doInitializeMarkerLayer, setDoInitializeMarkerLayer] = useState(false) const prevMarkersRef = useRef() @@ -121,7 +136,8 @@ const Leaflet = ({ icon: icon, draggable: m.draggable || false, autoPan: m.autoPan || false, - id: m.id + id: m.id, + zIndexOffset: 1000 }) if (m.name) { marker.bindPopup(m.name) @@ -166,6 +182,7 @@ const Leaflet = ({ const layerControl = new Control.Layers({}, {}, { collapsed: false }) layerControl.addTo(newMap) addLayers(newMap, layerControl) + setLayerControl(layerControl) setMap(newMap) @@ -238,20 +255,82 @@ const Leaflet = ({ widthPropUnchanged ]) + useEffect(() => { + if (!map || !layerControl || !allLocations?.length) { + return + } + const allMarkers = allLocations + .filter(loc => Location.hasCoordinates(loc)) + .map(location => { + const popupContent = document.createElement("div") + popupContent.setAttribute("style", "width: 300px;text-align: center") + + return new Marker([location.lat, location.lng], { + icon: locationIcon, + draggable: false, + autoPan: false, + id: location.uuid + }) + .bindTooltip(location.name, { + direction: "top", + permanent: true, + offset: [0, -58] + }) + .bindPopup(popupContent) + .on("popupopen", e => { + // TODO LinkTo component will be utilized here to provide routing + ReactDOM.render( + <> + {location.name} @{" "} + + , + e.popup.getContent() + ) + }) + }) + + const locationsLayer = new MarkerClusterGroup({ + iconCreateFunction: function(cluster) { + return new DivIcon({ + className: "all-locations-marker-cluster-icon-container", + html: ` + +
${cluster.getChildCount()}
+ ` + }) + } + }).addLayers(allMarkers) + + layerControl.addOverlay(locationsLayer, "All Locations") + locationsLayer.addTo(map) // make "All Locations" selected by default + + return () => { + layerControl.removeLayer(locationsLayer) + map.removeLayer(locationsLayer) + } + }, [map, layerControl, allLocations]) + return
} -Leaflet.propTypes = { +BaseLeaflet.propTypes = { width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), marginBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), markers: PropTypes.array, + allLocations: PropTypes.arrayOf(PropTypes.object).isRequired, mapId: PropTypes.string, // pass this when you have more than one map on a page onMapClick: PropTypes.func } -Leaflet.defaultProps = { +BaseLeaflet.defaultProps = { width: "100%", height: "500px", marginBottom: "18px" } +const Leaflet = props => ( + + {context => } + +) + export default Leaflet diff --git a/client/src/index.css b/client/src/index.css index 4e899688be..f9915fcc96 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1308,3 +1308,36 @@ div[id*='fg-entityAssessment'] { text-align: left; } } + +.all-locations-marker-cluster-icon-container { + border: 1px solid #3272cb; + position: relative !important; + border-radius: 6px; + background: white; +} + +.all-locations-marker-cluster-icon-container:hover { + z-index: 9999 !important; +} + +.all-locations-marker-cluster-icon-container .alm-cluster-icon { + transform-origin: bottom center; + transform: translate(-14px, -54px); + /* Prevents hover on parent container. If the mouse is on the image, parent container is not hovered while + other childs and parent itself remains hoverable. Image is much larger than the container and it overflows parent. + If hover is not prevented on the image then parent receives "z-index: 9999" which makes any marker beneath unclickable. */ + pointer-events: none; +} + +.all-locations-marker-cluster-icon-container .alm-cluster-text { + width: 34px; + height: 26px; + position: absolute; + bottom: 0; + left: 0; + transform: translate(3px, -36px); + font-size: 14px; + padding: 3px 0 0 5px; + color: white; + font-weight: bold; +} diff --git a/client/src/pages/App.js b/client/src/pages/App.js index aa0458b224..727053265e 100644 --- a/client/src/pages/App.js +++ b/client/src/pages/App.js @@ -148,6 +148,15 @@ const GQL_GET_APP_DATA = gql` shortName } } + + locationList(query: { pageSize: 0 }) { + list { + uuid + name + lat + lng + } + } } ` @@ -187,7 +196,8 @@ const App = ({ pageDispatchers, pageProps }) => { currentUser: appState.currentUser, loadAppData: refetch, notifications: appState.notifications, - connection: { ...connectionInfo } + connection: { ...connectionInfo }, + allLocations: appState.allLocations }} > { const currentUser = new Person(data.me) const notifications = getNotifications(currentUser.position) + const allLocations = data?.locationList?.list || [] + return { currentUser, settings, advisorOrganizations, principalOrganizations, - notifications + notifications, + allLocations } } } diff --git a/client/src/pages/locations/Form.js b/client/src/pages/locations/Form.js index 086dbc1408..e0f19f6eee 100644 --- a/client/src/pages/locations/Form.js +++ b/client/src/pages/locations/Form.js @@ -55,7 +55,7 @@ const LOCATION_TYPES_SUPER_USER = Settings?.fields?.location?.superUserTypeOptions const LocationForm = ({ edit, title, initialValues, notesComponent }) => { - const { currentUser } = useContext(AppContext) + const { currentUser, loadAppData } = useContext(AppContext) const history = useHistory() const [error, setError] = useState(null) const [showSimilarLocations, setShowSimilarLocations] = useState(false) @@ -353,6 +353,8 @@ const LocationForm = ({ edit, title, initialValues, notesComponent }) => { // reset the form to latest values // to avoid unsaved changes propmt if it somehow becomes dirty form.resetForm({ values, isSubmitting: true }) + // After successful submit, reload all locations data + loadAppData() if (!edit) { history.replace(Location.pathForEdit(location)) } diff --git a/client/src/resources/leaflet/marker-flag-blue-2x.png b/client/src/resources/leaflet/marker-flag-blue-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f6355df79a6d8c23ff3e4255f8ad16846339a652 GIT binary patch literal 1669 zcmdT_`#aMM82`>@Wwxa(w1vW=IHeHBB{GI|Rw%iIRZ4Q1+}UPZ!FJJ$AMF?6-_0|)i(wJG_cm8OqXw5Gy%&Z0Ueu;YDJKo0U6$oO0$^I zy2(Ay-`g+A3HPtGLHFXrHJUsJtWW0g>_DwMORJrcqmnxu6vtYXR;|zu-KiLFC=V8xBqnlx1=h~0UKEZFc7y9I=C%eI zayIeY`5%T>1=o5d7RKDZ&&gjGiQ3Sw;Sm^7LU5AI*Dm^9c;hJ7cG|{8LiI*6E`^9T z?4#j^b`gRPyhXB`zEP{`A8#z9muSLH?yO-x-xVZLx%mkX;68b*Ys{Cdih!sAR;(wT zx(#OA;B(lli9L#wUJM@H52BQZ-Tu0pbz71ffGXE>$Ze zy=Gvyi`uk-{KXgz|D*IRQz+YSUWdri=C**ABV?_9*`A3ur^dSDoPof!A9i%FeJ*J-X2>VYTIFEfB9oZ_n4%bQF{a09E2dh8|4pZfcXfSA+IF+zLXV&KLU0UlZskOZfo zQN(N$OMM}M*s3~{^Wa>m5R{`jISBbP^jt~3&Wix zHJZsr%z$<-?^U)wjJ*N!ytO0Q`gAGT+(FiBx=;3^MlnIG{&7DModxfW&WL)6hq|9z zbkFCzfg~IILYL2ly&)%-Tlwim?J(~gF!0E0L&!{sshPl5Ym#+&9|Sc303-Iqf%5;9fHJ48Yox zLztY`&OLrQr?efcQ2%oQi#3t;Kcms&$aDk7IH)y+;uUv-G~$QV+kd=_bZ`9~IPpsf|?5h0|#u_3{1=%k0Qa QioXK5ITM_oITA1Z4RamQTL1t6 literal 0 HcmV?d00001 diff --git a/client/src/resources/leaflet/marker-flag-blue.png b/client/src/resources/leaflet/marker-flag-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed3e1f6acd4283c882062983701caa2aef9c15c GIT binary patch literal 887 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GGLLkg|>2BR01_owL zPZ!6KiaBp*`)3O~N*tSCo>qQ&+1hLNp#mcLZyk>`OYke*W3_L-D(5BWzxUO_Zq|HT zPTeiy`x_oCoPH@g%aPy7ag7L<>#DV;TYb%TTd&UvnzUBTCS{xP($5Yz=AL|3JO5{@ zk@d`!j+up%RQ*a`%QEybWW4!nV;ftwPWxV<=V=KC|7mfG3kv^qF;3lItkiO5$64d0 z{d13f`Vw)7p-X6m%7Fy)3+uJtJp8mTjC+f0FO$Sui!eF1PR4^g2YdCdX_@z(&AVRp z?!m_iR$Jnwy$@B@EAmbfc+t(b{^!0>sihqoE{Y$x-8JD)$F;>Qm#r4bOEX9>X9@^i zxG%`t{{QvLWn0!NzuU;L;ij}gn!?YRweR`sqNL`Z`c-+rZ+d%dyhH6RUzHyd`zG3J zuVxHk?)2Ku9(6A`;E&EMt-TlL9*$y|DDvaxn@bo*LK?c(~0D- zHV@w9u_$u>3osO5`fL*O?)u!rzh*vY^^#o`erWftQa6s83$pjC7+!`yY@BQL=(F9C zqYt|5gX&hB?mL%Iso4>_Sv^LN9VEpUr=ki~avCVnwiuB4dja~0>$ zR9NcW%(Cp6mjB|el^b_Wwwti-!<+Roe`DQN&%6E9CpL;hk5wr9p)&*b+^DPk3xt|o zvv$6mXWW}18(sQa{Z5@+q`=x)W<2%!PPwKn>~U8T!}S^FH>|DToBAYr_d@pYxt5n5 z&Y4Uy4zQ>;{I^$(Q{TmSX{7A5uS^^ov6Ay1TwmI^`&r=0zeXvI?2C&SbUq}6@HMc? z^_*hdaG*KRl0mG(K#TnV6TjP2hKL6TSI97M+el1hP9RdTZ{faVea5Ugd9zM`-M@F& z(%kn2y9$NmbU!>fD{f}`p}uJLa$TT}>e37i_I_^SPwzWk(FW!u22WQ%mvv4FO#u3S Bf5-p; literal 0 HcmV?d00001 From b64542fedd562c5120470f5a489e5405bea422a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Mon, 12 Oct 2020 16:07:02 +0300 Subject: [PATCH 2/3] nci-agency/anet#3045: Fix geolocation printing no coordinates --- client/src/components/Leaflet.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/components/Leaflet.js b/client/src/components/Leaflet.js index 0417a5cc0e..427858d6ee 100644 --- a/client/src/components/Leaflet.js +++ b/client/src/components/Leaflet.js @@ -1,5 +1,6 @@ import AppContext from "components/AppContext" import GeoLocation from "components/GeoLocation" +import { convertLatLngToMGRS } from "geoUtils" import { Control, CRS, DivIcon, Icon, Map, Marker, TileLayer } from "leaflet" import "leaflet-defaulticon-compatibility" import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css" @@ -282,7 +283,16 @@ const BaseLeaflet = ({ ReactDOM.render( <> {location.name} @{" "} - + , e.popup.getContent() ) From e50becf789c8b19077cd1989c115469495c45bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Mon, 14 Dec 2020 13:37:32 +0300 Subject: [PATCH 3/3] nci-agency/anet#3045: Remove the link on the popup --- client/src/components/Leaflet.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/Leaflet.js b/client/src/components/Leaflet.js index 427858d6ee..745aed9a8f 100644 --- a/client/src/components/Leaflet.js +++ b/client/src/components/Leaflet.js @@ -279,7 +279,6 @@ const BaseLeaflet = ({ }) .bindPopup(popupContent) .on("popupopen", e => { - // TODO LinkTo component will be utilized here to provide routing ReactDOM.render( <> {location.name} @{" "}