diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 988f0b1a..4e4cdc4f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", + "@christopherpickering/react-leaflet-markercluster": "^1.1.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/base": "^5.0.0-beta.7", @@ -1939,6 +1940,23 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@christopherpickering/react-leaflet-markercluster": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@christopherpickering/react-leaflet-markercluster/-/react-leaflet-markercluster-1.1.0.tgz", + "integrity": "sha512-69Q3c/Szq7vXNSq6wy+wi6Wj4yHHVxzAuJMiFMgTcoyuZO4EQj8a6qzOc/XdcuQbNX+gfFe2Me/C61bc6sjO4g==", + "dependencies": { + "@react-leaflet/core": "^2.1.0", + "leaflet": "^1.9.3", + "leaflet.markercluster": "^1.5.3", + "postcss-flexbugs-fixes": "^5.0.2", + "react-leaflet": "^4.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.3", + "leaflet.markercluster": "^1.5.3", + "react-leaflet": "^4.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -10094,6 +10112,14 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" }, + "node_modules/leaflet.markercluster": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", + "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", + "peerDependencies": { + "leaflet": "^1.3.1" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10326,6 +10352,24 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -13905,6 +13949,42 @@ "node": ">=10.4.0" } }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8f05d3b8..eb32abe6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", + "@christopherpickering/react-leaflet-markercluster": "^1.1.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/base": "^5.0.0-beta.7", diff --git a/frontend/src/components/Map/index.tsx b/frontend/src/components/Map/index.tsx index d8669283..339dd644 100644 --- a/frontend/src/components/Map/index.tsx +++ b/frontend/src/components/Map/index.tsx @@ -4,10 +4,11 @@ import { MapContainer, GeoJSON, useMapEvents, TileLayer, Tooltip, Marker } from import { useTheme, LinearProgress } from '@mui/material'; import { UseAppStoreType, AppContext } from '../../contexts/AppContext'; import { GeoJsonObject } from 'geojson'; -import { Icon, LeafletMouseEvent, Point } from 'leaflet'; +import { Icon, LeafletMouseEvent } from 'leaflet'; import { PublicOrder } from '../../models'; import OrderTooltip from '../Charts/helpers/OrderTooltip'; import getWorldmapGeojson from '../../geo/Web'; +import MarkerClusterGroup from '@christopherpickering/react-leaflet-markercluster'; interface Props { orderType?: number; @@ -91,14 +92,20 @@ const Map = ({ }; const getOrderMarkers = () => { - return orders.map((order) => { - if (!order.latitude || !order.longitude) return <>; - return RobotMarker(order.id, [order.latitude, order.longitude], order.type || 0, order); - }); + if (orders.length < 1) return <>; + return ( + + {orders.map((order) => { + if (!order.latitude || !order.longitude) return <>; + return RobotMarker(order.id, [order.latitude, order.longitude], order.type || 0, order); + })} + + ); }; return ( svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { - pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: visiblePainted; + /* IE 9-10 doesn't have auto */ pointer-events: auto; } @@ -276,9 +320,11 @@ svg.leaflet-image-layer.leaflet-interactive path { background: #ddd; outline-offset: 1px; } + .leaflet-container a { color: #0078a8; } + .leaflet-zoom-box { border: 2px dotted #38f; background: rgba(255, 255, 255, 0.5); @@ -298,6 +344,7 @@ svg.leaflet-image-layer.leaflet-interactive path { box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); border-radius: 4px; } + .leaflet-bar a { background-color: #fff; border-bottom: 1px solid #ccc; @@ -309,25 +356,30 @@ svg.leaflet-image-layer.leaflet-interactive path { text-decoration: none; color: black; } + .leaflet-bar a, .leaflet-control-layers-toggle { background-position: 50% 50%; background-repeat: no-repeat; display: block; } + .leaflet-bar a:hover, .leaflet-bar a:focus { background-color: #f4f4f4; } + .leaflet-bar a:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; } + .leaflet-bar a:last-child { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-bottom: none; } + .leaflet-bar a.leaflet-disabled { cursor: default; background-color: #f4f4f4; @@ -339,10 +391,12 @@ svg.leaflet-image-layer.leaflet-interactive path { height: 30px; line-height: 30px; } + .leaflet-touch .leaflet-bar a:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } + .leaflet-touch .leaflet-bar a:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; @@ -371,47 +425,57 @@ svg.leaflet-image-layer.leaflet-interactive path { background: #fff; border-radius: 5px; } + .leaflet-control-layers-toggle { background-image: url(images/layers.png); width: 36px; height: 36px; } + .leaflet-retina .leaflet-control-layers-toggle { background-image: url(images/layers-2x.png); background-size: 26px 26px; } + .leaflet-touch .leaflet-control-layers-toggle { width: 44px; height: 44px; } + .leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { display: none; } + .leaflet-control-layers-expanded .leaflet-control-layers-list { display: block; position: relative; } + .leaflet-control-layers-expanded { padding: 6px 10px 6px 6px; color: #333; background: #fff; } + .leaflet-control-layers-scrollbar { overflow-y: scroll; overflow-x: hidden; padding-right: 5px; } + .leaflet-control-layers-selector { margin-top: 2px; position: relative; top: 1px; } + .leaflet-control-layers label { display: block; font-size: 13px; font-size: 1.08333em; } + .leaflet-control-layers-separator { height: 0; border-top: 1px solid #ddd; @@ -431,31 +495,38 @@ svg.leaflet-image-layer.leaflet-interactive path { background: rgba(255, 255, 255, 0.8); margin: 0; } + .leaflet-control-attribution, .leaflet-control-scale-line { padding: 0 5px; color: #333; line-height: 1.4; } + .leaflet-control-attribution a { text-decoration: none; } + .leaflet-control-attribution a:hover, .leaflet-control-attribution a:focus { text-decoration: underline; } + .leaflet-attribution-flag { display: inline !important; vertical-align: baseline !important; width: 1em; height: 0.6669em; } + .leaflet-left .leaflet-control-scale { margin-left: 5px; } + .leaflet-bottom .leaflet-control-scale { margin-bottom: 5px; } + .leaflet-control-scale-line { border: 2px solid #777; border-top: none; @@ -467,11 +538,13 @@ svg.leaflet-image-layer.leaflet-interactive path { background: rgba(255, 255, 255, 0.8); text-shadow: 1px 1px #fff; } + .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; border-bottom: none; margin-top: -2px; } + .leaflet-control-scale-line:not(:first-child):not(:last-child) { border-bottom: 2px solid #777; } @@ -481,6 +554,7 @@ svg.leaflet-image-layer.leaflet-interactive path { .leaflet-touch .leaflet-bar { box-shadow: none; } + .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { border: 2px solid rgba(0, 0, 0, 0.2); @@ -494,11 +568,13 @@ svg.leaflet-image-layer.leaflet-interactive path { text-align: center; margin-bottom: 20px; } + .leaflet-popup-content-wrapper { padding: 1px; text-align: left; border-radius: 12px; } + .leaflet-popup-content { margin: 13px 24px 13px 20px; line-height: 1.3; @@ -506,10 +582,12 @@ svg.leaflet-image-layer.leaflet-interactive path { font-size: 1.08333em; min-height: 1px; } + .leaflet-popup-content p { margin: 17px 0; margin: 1.3em 0; } + .leaflet-popup-tip-container { width: 40px; height: 20px; @@ -520,6 +598,7 @@ svg.leaflet-image-layer.leaflet-interactive path { overflow: hidden; pointer-events: none; } + .leaflet-popup-tip { width: 17px; height: 17px; @@ -533,12 +612,14 @@ svg.leaflet-image-layer.leaflet-interactive path { -ms-transform: rotate(45deg); transform: rotate(45deg); } + .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; color: #333; box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4); } + .leaflet-container a.leaflet-popup-close-button { position: absolute; top: 0; @@ -555,10 +636,12 @@ svg.leaflet-image-layer.leaflet-interactive path { text-decoration: none; background: transparent; } + .leaflet-container a.leaflet-popup-close-button:hover, .leaflet-container a.leaflet-popup-close-button:focus { color: #585858; } + .leaflet-popup-scrolled { overflow: auto; } @@ -566,6 +649,7 @@ svg.leaflet-image-layer.leaflet-interactive path { .leaflet-oldie .leaflet-popup-content-wrapper { -ms-zoom: 1; } + .leaflet-oldie .leaflet-popup-tip { width: 24px; margin: 0 auto; @@ -603,10 +687,12 @@ svg.leaflet-image-layer.leaflet-interactive path { pointer-events: none; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); } + .leaflet-tooltip.leaflet-interactive { cursor: pointer; pointer-events: auto; } + .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, .leaflet-tooltip-left:before, @@ -623,41 +709,50 @@ svg.leaflet-image-layer.leaflet-interactive path { .leaflet-tooltip-bottom { margin-top: 6px; } + .leaflet-tooltip-top { margin-top: -6px; } + .leaflet-tooltip-bottom:before, .leaflet-tooltip-top:before { left: 50%; margin-left: -6px; } + .leaflet-tooltip-top:before { bottom: 0; margin-bottom: -12px; border-top-color: #fff; } + .leaflet-tooltip-bottom:before { top: 0; margin-top: -12px; margin-left: -6px; border-bottom-color: #fff; } + .leaflet-tooltip-left { margin-left: -6px; } + .leaflet-tooltip-right { margin-left: 6px; } + .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { top: 50%; margin-top: -6px; } + .leaflet-tooltip-left:before { right: 0; margin-right: -12px; border-left-color: #fff; } + .leaflet-tooltip-right:before { left: 0; margin-left: -12px; @@ -673,3 +768,107 @@ svg.leaflet-image-layer.leaflet-interactive path { print-color-adjust: exact; } } + +/* react-leaflet-cluster https://unpkg.com/@christopherpickering/react-leaflet-markercluster@1.1.0/dist/styles.min.css */ + +.marker-cluster-small { + background-color: rgba(181, 226, 140, 0.6); +} + +.marker-cluster-small div { + background-color: rgba(110, 204, 57, 0.6); +} + +.marker-cluster-medium { + background-color: rgba(241, 211, 87, 0.6); +} + +.marker-cluster-medium div { + background-color: rgba(240, 194, 12, 0.6); +} + +.marker-cluster-large { + background-color: rgba(253, 156, 115, 0.6); +} + +.marker-cluster-large div { + background-color: rgba(241, 128, 23, 0.6); +} + +.leaflet-oldie .marker-cluster-small { + background-color: rgb(181, 226, 140); +} + +.leaflet-oldie .marker-cluster-small div { + background-color: rgb(110, 204, 57); +} + +.leaflet-oldie .marker-cluster-medium { + background-color: rgb(241, 211, 87); +} + +.leaflet-oldie .marker-cluster-medium div { + background-color: rgb(240, 194, 12); +} + +.leaflet-oldie .marker-cluster-large { + background-color: rgb(253, 156, 115); +} + +.leaflet-oldie .marker-cluster-large div { + background-color: rgb(241, 128, 23); +} + +.marker-cluster { + background-clip: padding-box; + border-radius: 20px; +} + +.marker-cluster div { + width: 30px; + height: 30px; + margin-left: 5px; + margin-top: 5px; + text-align: center; + border-radius: 15px; + font: + 12px 'Helvetica Neue', + Arial, + Helvetica, + sans-serif; +} + +.marker-cluster span { + line-height: 30px; +} + +.leaflet-cluster-anim .leaflet-marker-icon, +.leaflet-cluster-anim .leaflet-marker-shadow { + -webkit-transition: + -webkit-transform 0.3s ease-out, + opacity 0.3s ease-in; + -moz-transition: + -moz-transform 0.3s ease-out, + opacity 0.3s ease-in; + -o-transition: + -o-transform 0.3s ease-out, + opacity 0.3s ease-in; + transition: + transform 0.3s ease-out, + opacity 0.3s ease-in; +} + +.leaflet-cluster-spider-leg { + -webkit-transition: + -webkit-stroke-dashoffset 0.3s ease-out, + -webkit-stroke-opacity 0.3s ease-in; + -moz-transition: + -moz-stroke-dashoffset 0.3s ease-out, + -moz-stroke-opacity 0.3s ease-in; + -o-transition: + -o-stroke-dashoffset 0.3s ease-out, + -o-stroke-opacity 0.3s ease-in; + transition: + stroke-dashoffset 0.3s ease-out, + stroke-opacity 0.3s ease-in; +}