From b5c9bc27eddb5d79f55bf49fd30b50fdcd8336d7 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] nci-agency/anet#3045: Add all locations overlay to the map --- client/src/components/Leaflet.js | 82 ++++++++++++++++-- client/src/index.css | 30 +++++++ client/src/pages/App.js | 15 +++- .../resources/leaflet/marker-flag-blue.png | Bin 0 -> 2030 bytes 4 files changed, 121 insertions(+), 6 deletions(-) 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 32dc27b00f..8d1f0d6543 100644 --- a/client/src/components/Leaflet.js +++ b/client/src/components/Leaflet.js @@ -1,4 +1,5 @@ -import { Control, CRS, Icon, Map, Marker, TileLayer } from "leaflet" +import AppContext from "components/AppContext" +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 { @@ -14,8 +15,11 @@ import "leaflet.markercluster/dist/MarkerCluster.css" import "leaflet.markercluster/dist/MarkerCluster.Default.css" import "leaflet/dist/leaflet.css" import { Location } from "models" +import GeoLocation from "pages/locations/GeoLocation" import PropTypes from "prop-types" import React, { useCallback, useEffect, useRef, useState } from "react" +import ReactDOM from "react-dom" +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 +68,13 @@ const icon = new Icon({ shadowSize: [41, 41] }) +const locationIcon = new Icon({ + iconUrl: MARKER_FLAG_BLUE, + iconSize: [64, 64], + iconAnchor: [32, 64], + popupAnchor: [4, -58] +}) + const addLayers = (map, layerControl) => { let defaultLayer = null Settings.imagery.baseLayers.forEach(layerConfig => { @@ -85,11 +96,12 @@ const addLayers = (map, layerControl) => { } } -const Leaflet = ({ +const BaseLeaflet = ({ width, height, marginBottom, markers, + allLocations, mapId: initialMapId, onMapClick }) => { @@ -108,6 +120,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 +134,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 +180,7 @@ const Leaflet = ({ const layerControl = new Control.Layers({}, {}, { collapsed: false }) layerControl.addTo(newMap) addLayers(newMap, layerControl) + setLayerControl(layerControl) setMap(newMap) @@ -238,20 +253,77 @@ 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 + }, [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 334ad06d49..dc1bc8dc8b 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1097,3 +1097,33 @@ header.searchPagination { height: 100% !important; } } + +.all-locations-marker-cluster-icon-container { + border: 1px solid black; + 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(-27px, -57px); + /* 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, -39px); + font-size: 13px; + padding: 3px 0 0 7px; +} diff --git a/client/src/pages/App.js b/client/src/pages/App.js index b6c23bd63b..d62ef7d950 100644 --- a/client/src/pages/App.js +++ b/client/src/pages/App.js @@ -112,6 +112,15 @@ const GQL_GET_APP_DATA = gql` shortName } } + + locationList(query: { pageSize: 0 }) { + list { + uuid + name + lat + lng + } + } } ` @@ -148,6 +157,7 @@ const App = ({ pageDispatchers, pageProps }) => { value={{ appSettings: appState.settings, currentUser: appState.currentUser, + allLocations: appState.allLocations, loadAppData: refetch }} > @@ -191,11 +201,14 @@ const App = ({ pageDispatchers, pageProps }) => { setting => (settings[setting.key] = setting.value) ) + const allLocations = data?.locationList?.list || [] + return { currentUser, settings, advisorOrganizations, - principalOrganizations + principalOrganizations, + allLocations } } } 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..00dca0e1dfc101c05264f1a2cb6dec1c68ea11ca GIT binary patch literal 2030 zcmV|w#8 zEGo#NVo`)d6~r%zMJpjzRU`x@v7IQTiQ~pjJf4|%?_qK8n|V&giPPXbOU|*b?z>MO zzw`Z`?>YC!8^dFJYX)AWcjHHR{L%#5H)uQDoAf+o` z7gPilrxt~BokWQufN*W(tPUak&pZRN{n?P?s!ecg!6NhEXn$?u!{}S_+?Frll^FUO+{WR@8IR6GnAp zX=&x9-+cDi?;peu-3f5pF?SfHd@ZVI$g4tAMbuCw0}i_PnK zjDXr5Ma3on#nlt$4VII zeO~mm^FAfPJpoR_Av!TZ@<@u23w@tGy1#e+`PVN#wUr;;DFA0Mo(3%|z%=K)v?~2L z?`3M<$0)>oEK#0bMWaix|+`zrn8mw)&6p=o|}MnFnwk^+|30cvH%7xGlt ztN=}?rv*+HjwjZ4>g`eD5UbY)G z{VSJQAMm61hJ#CS?uoo4mb?>`vKthU@$qdAdOj*^s=udvo!rODNNLhMIVmOF5vyj7 z0~Z@VIwRol>gC0cKhfuJi+|_H;g9_CCoggP>dKAo2-x&aMr7+>+hOaB01-hXKfXKoof43jz~ezj)~Ny-0`{l^S_JG}1x(%y zb!FWV(7X*|kEVc`?d=pebLPz2o}LAOy`2KU-cAA7;|n3kueo4Pr@$Uw2f*VZn7aEdgEv_TWlr zKv$)2a>-kLeo;7yb~A32;^dc4-C0US0S`uil7aWg3O4Qr*&a9dQfo{z-W>#N|07M* zXV@J5X`tKS+kK=}D|Q6|+em@tB~cHUC!t(O60GL@L`F-T*!vy@0>GFRl>y&^-0V^vjdhGb|UW~B;-y?$@ zG65gA`a2I}ou)t?BO=0^Z@y72EiTRhbH%4f2Hh|Hj=etac0XG70?XI0&ygcvk--jt zb#@p$bWOnIBxsgN