import * as strings from 'ControlStrings' ; import * as React from 'react' ; import styles from './Maps.module.scss' ; import { IMapProps , ICoordinates , MapType , LocationInfo , IMapState } from '.' ; import { Label } from 'office-ui-fabric-react/lib/components/Label' ; import { Spinner , SpinnerSize } from 'office-ui-fabric-react/lib/Spinner' ; import { Icon } from "office-ui-fabric-react/lib/components/Icon" ; import { PrimaryButton } from "office-ui-fabric-react/lib/components/Button" ; import { TextField } from "office-ui-fabric-react/lib/components/TextField" ; import * as telemetry from '../../common/telemetry' ; import { isEqual } from "@microsoft/sp-lodash-subset" ; /**
 * Maps control
 */ export class Map extends React . Component < IMapProps , IMapState > { constructor ( props ) { super ( props ) ; const { coordinates } = this . props ; telemetry . track ( 'ReactMap' , { } ) ; this . state = { coordinates : coordinates || { latitude : null , longitude : null } , address : "" , showmessageerror : false , loading : false } ; } /**
     * componentWillMount lifecycle hook
     */ /**
     * componentWillUpdate lifecycle hook
     */ public componentWillUpdate ( nextProps , nextState ) { if ( ! isEqual ( this . props . coordinates , nextProps . coordinates ) ) { this . setState ( { coordinates : nextProps . coordinates } ) ; } } /**
    * Get the dif value based on zoom supplied (dif is for calculating the 4 corners of the map)
    */ private _getDif ( ) { const { zoom } = this . props ; const newZoom = zoom >= 0 ? zoom % 16 : 10 ; const multiplier = Math . floor ( newZoom / 16 ) + 1 ; return ( 0.0025 + ( 0.005 * ( 15 - ( newZoom ) ) ) ) / multiplier ; } /**
    * Get width as percentage
    */ private _getWidth ( ) { let widthToReturn = this . props . width ; if ( widthToReturn ) { let lastChar = widthToReturn . substr ( widthToReturn . length - 1 ) ; if ( lastChar !== '%' ) { widthToReturn = `${ widthToReturn }%` ; } } else { widthToReturn = "100%" ; } return widthToReturn ; } /**
    * Get height of the maps
    */ private _getHeight ( ) { return this . props . height ? this . props . height : 300 ; } /**
    * Get the type of the maps
    */ private _getMapType ( ) { return this . props . mapType ? this . props . mapType : MapType . standard ; } /**
    * Compute the url for the iframe
    */ private _getMapUrl ( ) { const dif = this . _getDif ( ) ; const mapType = this . _getMapType ( ) ; const coordinates = this . state . coordinates ; let mapUrl = "" ; if ( coordinates . latitude && coordinates . longitude ) { const bbox1 = coordinates . longitude - dif ; const bbox2 = coordinates . latitude - dif ; const bbox3 = coordinates . longitude + dif ; const bbox4 = coordinates . latitude + dif ; const rootUrl = "https://www.openstreetmap.org/export/embed.html" ; const qs = `?bbox=${ bbox1 },${ bbox2 },${ bbox3 },${ bbox4 }&layer=${ mapType }&marker=${ coordinates . latitude },${ coordinates . longitude }` ; mapUrl = rootUrl + qs ; } return mapUrl ; } private param { ( string | number ) } width param { ( string | number ) } height returns { string } memberof private _getBingMapUrl ( width , height ) { const { mapSource , zoom } = this . props ; const { coordinates : { latitude , longitude } } = this . state ; const mapType = mapSource === "BingStatic" ? "s" : "d" ; return `https://www.bing.com/maps/embed?h=${ height }&w=${ width }&cp=${ latitude }~${ longitude }&lvl=${ zoom }&typ=${ mapType }&sty=r` ; } /**
     * Get coordinates using the OpenStreetMap nominatim API
     */ private _getCoordinates = async ( ) : Promise < void > => { this . _startLoading ( ) ; try { const response = await fetch ( `https://nominatim.openstreetmap.org/search?format=json&limit=1&addressdetails=1&q=${ this . state . address }` ) ; const mapData = await response . json ( ) ; if ( mapData && mapData . length > 0 ) { const location = mapData [ 0 ] ; const coordinates = { latitude : parseFloat ( location . lat ) , longitude : parseFloat ( location . lon ) , displayName : location . display_name , address : location . address , } ; this . setState ( { coordinates , showmessageerror : false } ) ; if ( this . props . onUpdateCoordinates ) { this . props . onUpdateCoordinates ( coordinates ) ; } } } catch ( error ) { console . error ( error ) ; this . setState ( { showmessageerror : true } ) ; } this . _stopLoading ( ) ; } ; /**
    * Update address on submit (while searching is enabled)
    */ private _onChangedAddress = ( newValue ) : void => { this . setState ( { address : newValue , } ) ; } ; /**
    * Stop loading by changing status to null
    */ private _stopLoading ( ) { this . setState ( { loading : false } ) ; } /**
    * Start loading by changing status to Spinner
    */ private _startLoading ( ) { this . setState ( { loading : true } ) ; } /**
     * Default React render method
     */ public render ( ) { const { mapSource } = this . props ; let width = this . _getWidth ( ) ; let height = this . _getHeight ( ) ; let mapUrl = [ "BingDraggable" , "BingStatic" ] . indexOf ( mapSource ) !== - 1 ? this . _getBingMapUrl ( width , height ) : this . _getMapUrl ( ) ; return ( < div id = "mapsContainer" className = { `${ styles . mapContainer } ${ this . props . mapsClassName ? this . props . mapsClassName : '' }` } > { this . props . titleText && ( < Label > { this . props . titleText } < /Label>)} { ( this . props . enableSearch ) && ( < div id = "mapsSearch" className = { styles . searchContainer } > < TextField value = { this . state . address } onChange = { ( e , value ) => this . _onChangedAddress ( value ) } onKeyPress = { ( event ) => event . key === "Enter" ? this . _getCoordinates ( ) : null } iconProps = { { iconName : 'World' } } className = { styles . searchTextBox } / > < PrimaryButton text = { strings . mapsSearchButtonText } title = { strings . mapsSearchButtonText } className = { styles . submitButton } iconProps = { { iconName : 'Search' } } onClick = { this . _getCoordinates } / > < /div>)} { this . state . loading ? ( < Spinner size = { SpinnerSize . large } label = { this . props . loadingMessage ? this . props . loadingMessage : strings . mapsLoadingText } / > ) : ( ( mapUrl . length > 0 && ! this . state . showmessageerror ) ? ( < div id = "mapsIframe" > < iframe width = { width } height = { height } scrolling = "no" src = { mapUrl } > < /iframe> { mapSource === "BingStatic" && < Icon iconName = "Location" style = { { fontSize : "26px" , position : "relative" , top : ( Math . floor ( - height / 2 ) ) , left : "50%" , marginTop : "-14px" } } > < /Icon>}{/ * 20200614 - JJ - rudimentary bing map support ( draggable / static ) with pushpin ( static only ) * /} < /div>) : (<p className={`ms-TextField-errorMessage ${styles.errorMessage} ${this.props.errorMessageClassName ? this.props.errorMessageClassName : ''}`}> < Icon iconName = 'Error' className = { styles . errorIcon } / > < span data - automation - id = "error-message" > { this . props . errorMessage ? this . props . errorMessage : strings . mapsErrorMessage } < /span> < /p>))} < /div>); } }