From 7756b7d0b032ac817b882232c71b5304026791f4 Mon Sep 17 00:00:00 2001 From: Angie Rodriguez Date: Fri, 5 Jan 2024 09:08:40 -0800 Subject: [PATCH] Member map clustering (#1109) * Install react leaflet cluster and add temporary icon * Adjust temporary icon size and import leaflet styles * Add custom cluster icon and styling * Remove console.logs * Remove remaining console logs and comments * install leaflet.markercluster * apply type to cluster param * update colors * update classname to share * use member avatars when available * add a bit of shadow --------- Co-authored-by: Dan Ott --- app/components/MemberMap.tsx | 81 +++++++++++++++++++++++++++--------- package.json | 3 ++ pnpm-lock.yaml | 38 +++++++++++++++++ styles/_base.scss | 16 +++++++ 4 files changed, 119 insertions(+), 19 deletions(-) diff --git a/app/components/MemberMap.tsx b/app/components/MemberMap.tsx index 141cceba4..90adbd29f 100644 --- a/app/components/MemberMap.tsx +++ b/app/components/MemberMap.tsx @@ -1,6 +1,27 @@ import type { MappableMember } from 'members/types'; import { useEffect } from 'react'; +import L from 'leaflet'; import { MapContainer, Marker, TileLayer, Popup, useMap } from 'react-leaflet'; +import MarkerClusterGroup from 'react-leaflet-cluster'; +import 'leaflet/dist/leaflet.css'; + +const defaultCustomIcon = require('../../public/assets/images/virtual-coffee-mug-circle.svg'); + +const createClusterCustomIcon = function (cluster: L.MarkerCluster) { + return L.divIcon({ + html: `${cluster.getChildCount()}`, + className: 'leaflet-custom-marker', + iconSize: L.point(33, 33, true), + }); +}; + +const createCustomIcon = function (avatarUrl?: string) { + return new L.Icon.Default({ + iconUrl: avatarUrl || defaultCustomIcon, + iconSize: new L.Point(33, 33, true), + className: 'leaflet-custom-marker', + }); +}; function Markers({ members }: { members: MappableMember[] }) { const map = useMap(); @@ -16,42 +37,64 @@ function Markers({ members }: { members: MappableMember[] }) { ); }, [members, map]); + // avatarUrl + return ( <> - {members.map((member) => ( - - - {member.name} - {member.location?.title && ( - <> - {' - '} - {member.location?.title} - - )} - - - ))} + {members.map((member) => { + const customIcon = createCustomIcon(member.avatarUrl); + + return ( + + + {member.name} + {member.location?.title && ( + <> + {' - '} + {member.location?.title} + + )} + + + ); + })} ); } export default function MemberMap({ members }: { members: MappableMember[] }) { return ( -
+
- + + +
); diff --git a/package.json b/package.json index 377b6ebc5..fa10ad57e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "hast-util-to-string": "^2.0.0", "hastscript": "^8.0.0", "leaflet": "^1.9.4", + "leaflet.markercluster": "^1.5.3", "luxon": "^3.4.3", "markdown-it": "^13.0.2", "mdast-util-toc": "^7.0.0", @@ -80,6 +81,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", + "react-leaflet-cluster": "^2.1.0", "react-router": "^6.17.0", "react-router-dom": "^6.17.0", "rehype-autolink-headings": "^6.1.1", @@ -104,6 +106,7 @@ "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", "@types/leaflet": "^1.9.7", + "@types/leaflet.markercluster": "^1.5.4", "@types/luxon": "^3.3.3", "@types/react": "^18.2.32", "@types/react-dom": "^18.2.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c0eed5bc..816b77ca5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ dependencies: leaflet: specifier: ^1.9.4 version: 1.9.4 + leaflet.markercluster: + specifier: ^1.5.3 + version: 1.5.3(leaflet@1.9.4) luxon: specifier: ^3.4.3 version: 3.4.3 @@ -137,6 +140,9 @@ dependencies: react-leaflet: specifier: ^4.2.1 version: 4.2.1(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0) + react-leaflet-cluster: + specifier: ^2.1.0 + version: 2.1.0(leaflet@1.9.4)(react-dom@18.2.0)(react-leaflet@4.2.1)(react@18.2.0) react-router: specifier: ^6.17.0 version: 6.17.0(react@18.2.0) @@ -205,6 +211,9 @@ devDependencies: '@types/leaflet': specifier: ^1.9.7 version: 1.9.7 + '@types/leaflet.markercluster': + specifier: ^1.5.4 + version: 1.5.4 '@types/luxon': specifier: ^3.3.3 version: 3.3.3 @@ -4602,6 +4611,12 @@ packages: dependencies: '@types/node': 20.8.9 + /@types/leaflet.markercluster@1.5.4: + resolution: {integrity: sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg==} + dependencies: + '@types/leaflet': 1.9.7 + dev: true + /@types/leaflet@1.9.7: resolution: {integrity: sha512-FOfKB1ALYUDnXkH7LfTFreWiZr9R7GErqGP+8lYQGWr2GFq5+jy3Ih0M7e9j41cvRN65kLALJ4dc43yZwyl/6g==} dependencies: @@ -11053,6 +11068,14 @@ packages: readable-stream: 2.3.8 dev: true + /leaflet.markercluster@1.5.3(leaflet@1.9.4): + resolution: {integrity: sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==} + peerDependencies: + leaflet: ^1.3.1 + dependencies: + leaflet: 1.9.4 + dev: false + /leaflet@1.9.4: resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} dev: false @@ -14337,6 +14360,21 @@ packages: /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-leaflet-cluster@2.1.0(leaflet@1.9.4)(react-dom@18.2.0)(react-leaflet@4.2.1)(react@18.2.0): + resolution: {integrity: sha512-16X7XQpRThQFC4PH4OpXHimGg19ouWmjxjtpxOeBKpvERSvIRqTx7fvhTwkEPNMFTQ8zTfddz6fRTUmUEQul7g==} + peerDependencies: + leaflet: ^1.8.0 + react: ^18.0.0 + react-dom: ^18.0.0 + react-leaflet: ^4.0.0 + dependencies: + leaflet: 1.9.4 + leaflet.markercluster: 1.5.3(leaflet@1.9.4) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-leaflet: 4.2.1(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0) + dev: false + /react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==} peerDependencies: diff --git a/styles/_base.scss b/styles/_base.scss index 045ec0abf..2020127b1 100644 --- a/styles/_base.scss +++ b/styles/_base.scss @@ -312,6 +312,22 @@ ul { background: none; justify-content: flex-end; } + .text-sm { font-size: 0.8rem; } + +.leaflet-custom-marker { + background: #ffffff; + border-radius: 50%; + color: #000000; + height: 33px; + // line-height: 37px; + text-align: center; + width: 33px; + border: 3px solid $pink; + display: flex !important; + justify-content: center; + align-items: center; + box-shadow: 1px 1px 5px 0px $pink; +}