import * as React from 'react' ; import { AppState } from '../store' ; import { useDispatch , useSelector } from 'react-redux' ; import { Coordinates , Incident } from '@rdrnt/tps-calls-shared' ; import ReactMapboxGl , { Layer , Feature } from 'react-mapbox-gl' ; import { match } from 'react-router' ; import { toggleDrawer , openLoader , closeLoader , openModal , showToast , } from '../store/ui/actions' ; import { setSelectedIncident } from '../store/incidents/actions' ; import { setRequestingLocationPermissions } from '../store/user/actions' ; import { MAPBOX_THEME_URL , Colors , Sizes } from '../config' ; import { useScreenSize } from '../helpers/hooks' ; import { Environment , Analytics , Firebase } from '../helpers' ; import MapIncidentInfo from '../components/MapIncidentInfo' ; import MapOverlayButton from '../components/MapOverlayButton' ; import AnimatedMapMarker from '../components/MapMarker/Animated' ; const DEFAULTS = { latitude : 43.653225 , longitude : - 79.383186 , zoom : 11.0 , } ; interface MapState { position : Coordinates ; zoom : number ; } interface MapProps { match : match < { id ? : string ; } > ; } const MapMapbox = ReactMapboxGl ( { accessToken : Environment . config . MAPBOX_API_KEY , minZoom : 9 , attributionControl : false , } ) ; const Map = ( { match } ) => { const dispatch = useDispatch ( ) ; const { incidents , ui , user } = useSelector ( ( state ) => state ) ; const screenDimensions = useScreenSize ( ) ; const [ center , setCenter ] = React . useState < [ number , number ] > ( [ DEFAULTS . longitude , DEFAULTS . latitude , ] ) ; const [ mapState , setMapState ] = React . useState < MapState | undefined > ( ) ; const [ isMapLoaded , setIsMapLoaded ] = React . useState < boolean > ( false ) ; const [ interactingWithMap , setInteractingWithMap ] = React . useState < boolean > ( false ) ; const mapRef = React . useRef < any > ( ) ; const unselectIncidentWithAnimation = ( animated ? ) => { dispatch ( setSelectedIncident ( undefined ) ) ; if ( animated && mapRef . current && mapState ) { mapRef . current . flyTo ( { center : [ mapState . position . longitude , mapState . position . latitude ] , speed : 1 , zoom : mapState . zoom , } ) ; } setMapState ( undefined ) ; } ; const getIncidentWithId = async ( id , searchDB = false ) : Promise < Incident < any > | undefined > => { const matchingIncident = incidents . list . find ( incident => incident . id === id ) ; if ( ! matchingIncident && searchDB ) { const incidentFromDB = await Firebase . incidents . getIncidentFromId ( id ) ; return incidentFromDB ; } return matchingIncident ; } ; React . useEffect ( ( ) => { if ( ! isMapLoaded && ! ui . loader . open ) { dispatch ( openLoader ( 'Loading map...' ) ) ; Analytics . pageview ( '/map' ) ; } if ( isMapLoaded && incidents . list . length !== 0 ) { if ( ui . loader . open ) { const apiStatus = Firebase . getRemoteConfigStringValue ( 'tps_api_status' ) ; if ( apiStatus === 'offline' ) { dispatch ( showToast ( { message : 'TPS feed offline' , options : { intent : 'error' , } , } ) ) ; } setTimeout ( ( ) => { dispatch ( closeLoader ( ) ) ; } , 500 ) ; } const { id } = match . params ; if ( id ) { getIncidentWithId ( id , true ) . then ( incident => { if ( ! incident ) { dispatch ( showToast ( { message : 'Incident no longer exists' , options : { intent : 'error' , } , } ) ) ; } else { dispatch ( setSelectedIncident ( incident ) ) ; } } ) ; } } } , [ isMapLoaded , incidents . list . length , match . params . id ] ) ; React . useEffect ( ( ) => { if ( interactingWithMap ) { if ( ui . drawerOpen ) { dispatch ( toggleDrawer ( false ) ) ; } if ( incidents . selected ) { dispatch ( setSelectedIncident ( undefined ) ) ; } } } , [ interactingWithMap ] ) ; React . useEffect ( ( ) => { if ( user . location . coordinates ) { setCenter ( [ user . location . coordinates . longitude , user . location . coordinates . latitude , ] ) ; } } , [ user . location . coordinates ] ) ; React . useEffect ( ( ) => { if ( incidents . selected && mapRef . current ) { const currentPosition = mapRef . current . getCenter ( ) ; setMapState ( { zoom : mapRef . current . getZoom ( ) , position : { latitude : currentPosition . lat , longitude : currentPosition . lng , } , } ) ; mapRef . current . flyTo ( { center : [ incidents . selected . coordinates . longitude , incidents . selected . coordinates . latitude , ] , speed : 1 , zoom : 15 , } ) ; } } , [ incidents . selected ] ) ; React . useEffect ( ( ) => { if ( mapRef . current ) { mapRef . current . resize ( ) ; } } , [ screenDimensions ] ) ; return ( < MapMapbox style = { MAPBOX_THEME_URL } containerStyle = { { height : screenDimensions . height , width : screenDimensions . width , } } center = { center } onStyleLoad = { map => { mapRef . current = map ; setIsMapLoaded ( true ) ; } } onDragStart = { ( ) => { setInteractingWithMap ( true ) ; } } onDragEnd = { ( ) => { setInteractingWithMap ( false ) ; } } onClick = { ( ) => { if ( ui . drawerOpen ) { dispatch ( toggleDrawer ( false ) ) ; } if ( incidents . selected ) { dispatch ( setSelectedIncident ( undefined ) ) ; } } } > { } < MapOverlayButton hidden = { ui . drawerOpen } onClick = { ( ) => dispatch ( toggleDrawer ( true ) ) } iconName = "menu" position = { { top : Sizes . SPACING , left : Sizes . SPACING } } /> { } < MapOverlayButton hidden = { Boolean ( ui . drawerOpen || incidents . selected ) } onClick = { ( ) => dispatch ( openModal ( 'project-info' ) ) } iconName = "info" position = { { bottom : Sizes . SPACING , right : Sizes . SPACING } } size = { 30 } / > { } < MapOverlayButton hidden = { Boolean ( ui . drawerOpen || incidents . selected || ! user . location . available ) } onClick = { ( ) => dispatch ( setRequestingLocationPermissions ( true ) ) } iconName = "position" position = { { bottom : Sizes . SPACING , right : Sizes . SPACING * 4 } } size = { 30 } / > < MapIncidentInfo incident = { incidents . selected } drawerOpen = { ui . drawerOpen } close = { ( ) => unselectIncidentWithAnimation ( true ) } /> { user . location . coordinates && ( < AnimatedMapMarker color = { Colors . BLACK } coordinates = { user . location . coordinates } size = { 10 } / > ) } { incidents . selected && ( < AnimatedMapMarker color = "#007bff" coordinates = { incidents . selected . coordinates } size = { 20 } / > ) } < Layer type = "circle" id = "marker" paint = { { 'circle-radius' : { stops : [ [ 9 , 4 ] , [ 11 , 6 ] , [ 14 , 8 ] , ] , } , 'circle-color' : Colors . PRIMARY , 'circle-stroke-width' : 1 , 'circle-stroke-color' : '#FFFFFF' , } } > { } { incidents . list . map ( incident => { const selected = Boolean ( incidents . selected && incidents . selected . id === incident . id ) ; if ( ! selected ) { return ( < Feature key = { incident . id } coordinates = { [ incident . coordinates . longitude , incident . coordinates . latitude , ] } onClick = { ( ) => { dispatch ( setSelectedIncident ( incident ) ) ; } } />); } return null ; } ) . filter ( incidentFeature => Boolean ( incidentFeature ) ) } < /Layer> < /MapMapbox>); } ; export default Map ;