diff --git a/client/src/components/Leaflet.js b/client/src/components/Leaflet.js
index 32dc27b00f..618276b3bb 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,12 @@ 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_FLAG_BLUE_2X from "resources/leaflet/marker-flag-blue-2x.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 334ad06d49..cf6e28c438 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -1097,3 +1097,36 @@ header.searchPagination {
height: 100% !important;
}
}
+
+.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 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/pages/locations/Form.js b/client/src/pages/locations/Form.js
index 60623989f9..b39c8de4e3 100644
--- a/client/src/pages/locations/Form.js
+++ b/client/src/pages/locations/Form.js
@@ -30,7 +30,13 @@ const GQL_UPDATE_LOCATION = gql`
}
`
-const BaseLocationForm = ({ currentUser, edit, title, initialValues }) => {
+const BaseLocationForm = ({
+ currentUser,
+ edit,
+ title,
+ initialValues,
+ loadAppData
+}) => {
const history = useHistory()
const [error, setError] = useState(null)
const canEditName =
@@ -228,6 +234,8 @@ const BaseLocationForm = ({ currentUser, edit, title, initialValues }) => {
? response[operation].uuid
: initialValues.uuid
})
+ // After successful submit, reload all locations data
+ loadAppData()
// After successful submit, reset the form in order to make sure the dirty
// prop is also reset (otherwise we would get a blocking navigation warning)
form.resetForm()
@@ -251,6 +259,7 @@ BaseLocationForm.propTypes = {
initialValues: PropTypes.instanceOf(Location).isRequired,
title: PropTypes.string,
edit: PropTypes.bool,
+ loadAppData: PropTypes.func.isRequired,
currentUser: PropTypes.instanceOf(Person)
}
@@ -263,7 +272,11 @@ BaseLocationForm.defaultProps = {
const LocationForm = props => (
{context => (
-
+
)}
)
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 0000000000..f6355df79a
Binary files /dev/null and b/client/src/resources/leaflet/marker-flag-blue-2x.png differ
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 0000000000..6ed3e1f6ac
Binary files /dev/null and b/client/src/resources/leaflet/marker-flag-blue.png differ