11/* eslint-disable */
22
33import React from 'react' ;
4+ import ReactDOM from 'react-dom' ;
45import PropTypes from 'prop-types' ;
56import { connect } from 'react-redux' ;
67import withStyles from '@mui/styles/withStyles' ;
78import mapboxgl from 'mapbox-gl' ;
89import FilterMenu from '@components/main/Desktop/FilterMenu' ;
10+ import Tooltip from '@mui/material/Tooltip' ;
911// import LocationDetail from './LocationDetail';
1012import { REQUEST_TYPES } from '@components/common/CONSTANTS' ;
1113import { getNcByLngLat , clearPinInfo } from '@reducers/data' ;
@@ -49,6 +51,8 @@ import RequestDetail from './RequestDetail';
4951import { debounce , isEmpty } from '@utils' ;
5052
5153import settings from '@settings' ;
54+ import ZoomTooltip from './zoomTooltip' ;
55+ import { DEFAULT_MIN_ZOOM , DEFAULT_MAX_ZOOM } from '@components/common/CONSTANTS' ;
5256
5357const styles = ( theme ) => ( {
5458 root : {
@@ -60,9 +64,8 @@ const styles = (theme) => ({
6064 '& canvas.mapboxgl-canvas:focus' : {
6165 outline : 'none' ,
6266 } ,
63- // TODO: controls placement
6467 '& .mapboxgl-control-container' : {
65- display : 'none' ,
68+ // TODO: update styles here when design finalized
6669 } ,
6770 '& .mapboxgl-popup-content' : {
6871 width : 'auto' ,
@@ -100,7 +103,7 @@ const styles = (theme) => ({
100103} ) ;
101104
102105// Define feature layers
103- const featureLayers = [ 'request-circles' , 'nc-fills' ] ;
106+ const featureLayers = [ 'request-circles' , 'nc-fills' ] ;
104107
105108class Map extends React . Component {
106109 // Note: 'this.context' is defined using the static contextType property
@@ -151,12 +154,22 @@ class Map extends React.Component {
151154 pitchWithRotate : false ,
152155 dragRotate : false ,
153156 touchZoomRotate : false ,
157+ minZoom : DEFAULT_MIN_ZOOM ,
158+ maxZoom : DEFAULT_MAX_ZOOM ,
154159 } ) ;
155160
156161 map . on ( 'load' , ( ) => {
157162 if ( this . isSubscribed ) {
158163 this . initLayers ( true ) ;
159164
165+ map . addControl (
166+ new mapboxgl . NavigationControl ( {
167+ visualizePitch : false ,
168+ showCompass : false ,
169+ } ) ,
170+ 'bottom-right'
171+ ) ;
172+
160173 map . on ( 'click' , this . debouncedOnClick ) ;
161174 map . on ( 'mouseenter' , 'request-circles' , this . onMouseEnter ) ;
162175 map . on ( 'mouseleave' , 'request-circles' , this . onMouseLeave ) ;
@@ -178,16 +191,20 @@ class Map extends React.Component {
178191 const entireMapLoadTime = ( ) => {
179192 if ( this . map . isSourceLoaded ( 'requests' ) ) {
180193 const { dbStartTime } = this . context ;
181- const pinLoadEndTime = performance . now ( )
182- console . log ( `Pin load time: ${ Math . floor ( pinLoadEndTime - dbStartTime ) } ms` )
194+ const pinLoadEndTime = performance . now ( ) ;
195+ console . log (
196+ `Pin load time: ${ Math . floor (
197+ pinLoadEndTime - dbStartTime
198+ ) } ms`
199+ ) ;
183200 this . map . off ( 'idle' , entireMapLoadTime ) ;
184201 }
185- }
186-
202+ } ;
203+
187204 if ( this . props . requests != prevProps . requests ) {
188205 if ( this . state . mapReady ) {
189206 this . setState ( { requests : this . props . requests } ) ;
190- this . map . on ( 'idle' , entireMapLoadTime )
207+ this . map . on ( 'idle' , entireMapLoadTime ) ;
191208 // this.map.once('idle', this.setFilteredRequestCounts);
192209 } else {
193210 this . map . once ( 'idle' , ( ) => {
@@ -203,6 +220,50 @@ class Map extends React.Component {
203220 // });
204221 // }
205222 this . map . on ( 'load' , ( ) => {
223+ // grab the Zoom Out button of the Mapbox zoom controls
224+ const zoomOutControl = document . querySelector (
225+ '.mapboxgl-ctrl-zoom-out'
226+ ) ;
227+
228+ // use state to control tooltip's visibility
229+ let showZoomTooltip = false ;
230+
231+ // if zoom controls aren't limited, add the 'Zoom out' title back
232+ if ( ! showZoomTooltip ) {
233+ zoomOutControl . title = 'Zoom out' ;
234+ }
235+
236+ // render the zoomtooltip component
237+ const renderZoomTooltip = ( ) => {
238+ ReactDOM . render (
239+ < ZoomTooltip show = { showZoomTooltip } /> ,
240+ zoomOutControl
241+ ) ;
242+ } ;
243+
244+ // show the zoomtooltip on hover if the map is locked onto an ncLayer AND
245+ // the zoom out control is disabled
246+ const handleMouseEnter = ( ) => {
247+ // check if the current zoom level (this.map.getZoom()) is at or below minZoom,
248+ // indicating the map is zoomed in to its minimum level & the zoom out control is disabled
249+ const isZoomOutDisabled =
250+ this . map . getZoom ( ) <= this . state . minZoom ;
251+ if ( this . state . filterGeo && isZoomOutDisabled ) {
252+ showZoomTooltip = true ;
253+ renderZoomTooltip ( ) ;
254+ }
255+ } ;
256+
257+ // hide the zoomtooltip on mouse leave
258+ const handleMouseLeave = ( ) => {
259+ showZoomTooltip = false ;
260+ renderZoomTooltip ( ) ;
261+ } ;
262+
263+ // add hover event listeners to the zoomOutControl
264+ zoomOutControl . addEventListener ( 'mouseenter' , handleMouseEnter ) ;
265+ zoomOutControl . addEventListener ( 'mouseleave' , handleMouseLeave ) ;
266+
206267 if (
207268 this . state . filterGeo !== prevState . filterGeo ||
208269 this . state . selectedTypes !== prevState . selectedTypes
@@ -216,6 +277,7 @@ class Map extends React.Component {
216277 sourceId : 'nc' ,
217278 sourceData : this . props . ncBoundaries ,
218279 idProperty : 'NC_ID' ,
280+
219281 onSelectRegion : ( geo ) => {
220282 this . setState ( {
221283 locationInfo : {
@@ -228,7 +290,17 @@ class Map extends React.Component {
228290 } ,
229291 } ) ;
230292 this . map . once ( 'zoomend' , ( ) => {
231- this . setState ( { filterGeo : geo } ) ;
293+ this . setState ( ( prevState ) => {
294+ const newMinZoom = this . map . getZoom ( ) ;
295+ this . map . setMinZoom ( newMinZoom ) ;
296+ return {
297+ filterGeo : geo ,
298+ minZoom : newMinZoom ,
299+ } ;
300+ } ) ;
301+
302+ // initial render
303+ renderZoomTooltip ( ) ;
232304 } ) ;
233305 } ,
234306 onHoverRegion : ( geo ) => {
@@ -257,12 +329,16 @@ class Map extends React.Component {
257329 ncBoundaries
258330 ) {
259331 try {
260- const selectedCouncilId = Number ( this . initialState . councilId ) ;
332+ const selectedCouncilId = Number (
333+ this . initialState . councilId
334+ ) ;
261335 const newSelectedCouncil = councils . find (
262336 ( { councilId } ) => councilId === selectedCouncilId
263337 ) ;
264338 if ( ! newSelectedCouncil ) {
265- throw new Error ( 'Council Does not exist from search query' ) ;
339+ throw new Error (
340+ 'Council Does not exist from search query'
341+ ) ;
266342 }
267343 const newSelected = [ newSelectedCouncil ] ;
268344 dispatchUpdateSelectedCouncils ( newSelected ) ;
@@ -278,7 +354,9 @@ class Map extends React.Component {
278354
279355 if ( this . props . selectedNcId !== prevProps . selectedNcId ) {
280356 const { councils, selectedNcId } = this . props ;
281- const nc = councils . find ( ( { councilId } ) => councilId === selectedNcId ) ;
357+ const nc = councils . find (
358+ ( { councilId } ) => councilId === selectedNcId
359+ ) ;
282360 this . setState ( { selectedNc : nc } ) ;
283361 return this . ncLayer . selectRegion ( selectedNcId ) ;
284362 }
@@ -301,9 +379,9 @@ class Map extends React.Component {
301379 ...( center
302380 ? {
303381 locationInfo : {
304- location : `${ center . lat . toFixed ( 6 ) } N ${ center . lng . toFixed (
382+ location : `${ center . lat . toFixed (
305383 6
306- ) } E`,
384+ ) } N ${ center . lng . toFixed ( 6 ) } E`,
307385 radius : 1 ,
308386 nc : ncInfoFromLngLat ( center ) ,
309387 } ,
@@ -334,7 +412,9 @@ class Map extends React.Component {
334412 } ,
335413 onHoverRegion : ( geo ) => {
336414 this . setState ( {
337- hoveredRegionName : geo ? ccNameFromId ( geo . properties . name ) : null ,
415+ hoveredRegionName : geo
416+ ? ccNameFromId ( geo . properties . name )
417+ : null ,
338418 } ) ;
339419 } ,
340420 } ) ;
@@ -381,6 +461,9 @@ class Map extends React.Component {
381461 canReset : true ,
382462 } ) ;
383463 } ) ;
464+
465+ // Reset MinZoom to original value after deselecting NC
466+ this . map . setMinZoom ( DEFAULT_MIN_ZOOM ) ;
384467 } ;
385468
386469 resetBoundaries = ( ) => {
@@ -400,7 +483,8 @@ class Map extends React.Component {
400483 } ;
401484
402485 addressSearchIsEmpty = ( ) => {
403- const addressSearchInput = document . querySelector ( '#geocoder input' ) ;
486+ const addressSearchInput =
487+ document . querySelector ( '#geocoder input' ) ;
404488 return ! Boolean ( addressSearchInput ?. value ?. trim ( ) ) ;
405489 } ;
406490
@@ -440,7 +524,8 @@ class Map extends React.Component {
440524 // Display pop-ups only for the current district
441525 if (
442526 features [ i ] . properties . council_id &&
443- this . props . selectedNcId !== features [ i ] . properties . council_id
527+ this . props . selectedNcId !==
528+ features [ i ] . properties . council_id
444529 ) {
445530 return ;
446531 }
@@ -456,7 +541,7 @@ class Map extends React.Component {
456541 } ;
457542
458543 onMouseLeave = ( e ) => {
459- this . props . dispatchClearPinInfo ( )
544+ this . props . dispatchClearPinInfo ( ) ;
460545 this . removePopup ( ) ;
461546 } ;
462547
@@ -473,26 +558,26 @@ class Map extends React.Component {
473558 const features = this . getAllFeaturesAtPoint ( e . point ) ;
474559 for ( let i = 0 ; i < features . length ; i += 1 ) {
475560 const feature = features [ i ] ;
476- if ( feature . layer . id == " nc-fills" ) {
477- this . setState ( { address : null } ) ;
561+ if ( feature . layer . id == ' nc-fills' ) {
562+ this . setState ( { address : null } ) ;
478563
479- this . resetAddressSearch ( ) ; // Clear address search input
480- dispatchCloseBoundaries ( ) ; // Collapse boundaries section
564+ this . resetAddressSearch ( ) ; // Clear address search input
565+ dispatchCloseBoundaries ( ) ; // Collapse boundaries section
481566
482- const selectedCouncilId = Number ( feature . properties . NC_ID ) ;
483- const newSelectedCouncil = councils . find (
484- ( { councilId } ) => councilId === selectedCouncilId
485- ) ;
486- const newSelected = isEmpty ( newSelectedCouncil )
487- ? null
488- : [ newSelectedCouncil ] ;
567+ const selectedCouncilId = Number ( feature . properties . NC_ID ) ;
568+ const newSelectedCouncil = councils . find (
569+ ( { councilId } ) => councilId === selectedCouncilId
570+ ) ;
571+ const newSelected = isEmpty ( newSelectedCouncil )
572+ ? null
573+ : [ newSelectedCouncil ] ;
489574
490- dispatchUpdateSelectedCouncils ( newSelected ) ;
491- dispatchUpdateUnselectedCouncils ( councils ) ;
492- dispatchUpdateNcId ( selectedCouncilId ) ;
493-
494- return this . ncLayer . selectRegion ( feature . id ) ;
495- } else {
575+ dispatchUpdateSelectedCouncils ( newSelected ) ;
576+ dispatchUpdateUnselectedCouncils ( councils ) ;
577+ dispatchUpdateNcId ( selectedCouncilId ) ;
578+
579+ return this . ncLayer . selectRegion ( feature . id ) ;
580+ } else {
496581 return null ;
497582 }
498583 }
@@ -514,7 +599,7 @@ class Map extends React.Component {
514599 dispatchCloseBoundaries,
515600 dispatchUpdateSelectedCouncils,
516601 dispatchUpdateUnselectedCouncils,
517- councils
602+ councils,
518603 } = this . props ;
519604
520605 // Reset boundaries input
@@ -532,21 +617,29 @@ class Map extends React.Component {
532617 } else {
533618 // When result.properties.type does not equal "District"
534619 const [ longitude , latitude ] = result . center ;
535- const address = result . place_name . split ( ',' ) . slice ( 0 , - 2 ) . join ( ', ' ) ;
536-
537- const ncIdOfAddressSearch = getNcByLngLatv2 ( { longitude, latitude } ) ;
620+ const address = result . place_name
621+ . split ( ',' )
622+ . slice ( 0 , - 2 )
623+ . join ( ', ' ) ;
624+
625+ const ncIdOfAddressSearch = getNcByLngLatv2 ( {
626+ longitude,
627+ latitude,
628+ } ) ;
538629 if ( ! isEmpty ( ncIdOfAddressSearch ) ) {
539- //Adding name pill to search bar
630+ //Adding name pill to search bar
540631 const newSelectedCouncil = councils . find (
541- ( { councilId } ) => councilId === ncIdOfAddressSearch ,
632+ ( { councilId } ) => councilId === ncIdOfAddressSearch
542633 ) ;
543634 if ( ! newSelectedCouncil ) {
544- throw new Error ( 'Council Id in address search geocoder result could not be found' ) ;
635+ throw new Error (
636+ 'Council Id in address search geocoder result could not be found'
637+ ) ;
545638 }
546639 const newSelected = [ newSelectedCouncil ] ;
547640 dispatchUpdateSelectedCouncils ( newSelected ) ;
548641 dispatchUpdateUnselectedCouncils ( councils ) ;
549-
642+
550643 dispatchUpdateNcId ( Number ( ncIdOfAddressSearch ) ) ;
551644 this . setState ( {
552645 address : address ,
@@ -562,7 +655,7 @@ class Map extends React.Component {
562655 center : [ longitude , latitude ] ,
563656 essential : true ,
564657 zoom : 9 ,
565- } ) ;
658+ } ) ;
566659 return this . addressLayer . addMarker ( [ longitude , latitude ] ) ;
567660 }
568661 }
@@ -613,15 +706,19 @@ class Map extends React.Component {
613706 }
614707 } ) ( ) ;
615708
616- return Object . keys ( counts [ regionId ] ) . reduce ( ( filteredCounts , rType ) => {
617- if ( selectedTypes . includes ( rType ) )
618- filteredCounts [ rType ] = counts [ regionId ] [ rType ] ;
619- return filteredCounts ;
620- } , { } ) ;
709+ return Object . keys ( counts [ regionId ] ) . reduce (
710+ ( filteredCounts , rType ) => {
711+ if ( selectedTypes . includes ( rType ) )
712+ filteredCounts [ rType ] = counts [ regionId ] [ rType ] ;
713+ return filteredCounts ;
714+ } ,
715+ { }
716+ ) ;
621717 } ;
622718
623719 setFilteredRequestCounts = ( ) => {
624- const { requests, filterGeo, geoFilterType, selectedTypes } = this . state ;
720+ const { requests, filterGeo, geoFilterType, selectedTypes } =
721+ this . state ;
625722 const { ncCounts, ccCounts } = this . props ;
626723
627724 // use pre-calculated values for nc and cc filters if available
@@ -695,7 +792,10 @@ class Map extends React.Component {
695792 const { classes } = this . props ;
696793
697794 return (
698- < div className = { classes . root } ref = { ( el ) => ( this . mapContainer = el ) } >
795+ < div
796+ className = { classes . root }
797+ ref = { ( el ) => ( this . mapContainer = el ) }
798+ >
699799 < RequestsLayer
700800 ref = { ( el ) => ( this . requestsLayer = el ) }
701801 activeLayer = { activeRequestsLayer }
0 commit comments