-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Nick Maher
committed
Mar 10, 2017
1 parent
ce51f4b
commit ec7adda
Showing
13 changed files
with
558 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.root { | ||
position: absolute; | ||
transform: translate(-50%, -100%); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React, { Component, PropTypes } from 'react'; | ||
|
||
import css from './InteractiveMarker.css'; | ||
|
||
export default class InteractiveMarker extends Component { | ||
static propTypes = { | ||
active: PropTypes.bool, | ||
id: React.PropTypes.oneOfType([ | ||
React.PropTypes.string, | ||
React.PropTypes.number, | ||
]).isRequired, | ||
MarkerComponent: PropTypes.func.isRequired, | ||
onClick: PropTypes.func, | ||
props: PropTypes.object, | ||
}; | ||
|
||
handleClick = (e) => { | ||
const { onClick, id } = this.props; | ||
onClick(e, id); | ||
}; | ||
|
||
render() { | ||
const { MarkerComponent, active, props } = this.props; | ||
return ( | ||
<div className={ css.root }> | ||
<MarkerComponent | ||
{ ...props } | ||
active={ active } | ||
onClick={ this.handleClick } | ||
/> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import { render } from 'react-dom'; | ||
import InteractiveMarker from './InteractiveMarker'; | ||
|
||
const MarkerComponent = () => <button />; | ||
|
||
it('renders without crashing', () => { | ||
const div = document.createElement('div'); | ||
render(<InteractiveMarker id={ 1 } MarkerComponent={ MarkerComponent } />, div); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,80 @@ | ||
import React, { Component } from 'react'; | ||
import { storiesOf } from '@kadira/storybook'; | ||
import MarkableMap from './MarkableMap'; | ||
import BaseMap from './BaseMap'; | ||
import Marker from './SpaceMarker'; | ||
|
||
const generateMarkers = (number = 1) => { | ||
const markers = []; | ||
|
||
for (let i = 0; i < number; i += 1) { | ||
const lng = -0.09 + ((Math.random() - Math.random()) * Math.random()); | ||
const lat = 51.505 + ((Math.random() - Math.random()) * Math.random()); | ||
|
||
markers.push({ | ||
id: i, | ||
lngLat: [lng, lat], | ||
props: { | ||
price: '£322', | ||
priceUnit: '/day', | ||
location: 'Shoreditch', | ||
city: 'London', | ||
size: '1000 sqft', | ||
name: 'Bold Street Shop', | ||
images: [{ | ||
src: 'https://source.unsplash.com/random/500x503', | ||
alt: 'hello', | ||
}, { | ||
src: 'https://source.unsplash.com/random/500x500', | ||
alt: 'hello2', | ||
}, { | ||
src: 'https://source.unsplash.com/random/500x502', | ||
alt: 'hello', | ||
}, { | ||
src: 'https://source.unsplash.com/random/500x501', | ||
alt: 'hello2', | ||
}], | ||
href: '#', | ||
}, | ||
}); | ||
} | ||
return markers; | ||
}; | ||
|
||
class TestMap extends Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
markers: generateMarkers(10), | ||
}; | ||
} | ||
|
||
toggleMarkers = () => { | ||
this.setState({ markers: generateMarkers(Math.floor(Math.random() * 20)) }); | ||
} | ||
|
||
render() { | ||
const { markers } = this.state; | ||
return ( | ||
<div style={ { height: '93vh' } }> | ||
<button onClick={ this.toggleMarkers }>Randomise</button> | ||
<MarkableMap | ||
markers={ markers } | ||
MarkerComponent={ Marker } | ||
onClick={ actionWithComplexArgs('map clicked') } | ||
onMoveEnd={ actionWithComplexArgs('map moved') } | ||
autoFit | ||
/> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
storiesOf('Map', module) | ||
.add('Default', () => ( | ||
<div style={ { height: '96vh' } }><BaseMap /></div> | ||
)) | ||
)) | ||
.add('MarkableMap', () => ( | ||
<TestMap /> | ||
)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.marker { | ||
position: absolute; | ||
z-index: 1; | ||
} | ||
|
||
.markerActive { | ||
z-index: 2; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import React, { Component, PropTypes } from 'react'; | ||
/* eslint-disable camelcase */ | ||
import { | ||
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer, | ||
unmountComponentAtNode, | ||
} from 'react-dom'; | ||
/* eslint-enable camelcase */ | ||
import isEqual from 'lodash/fp/isEqual'; | ||
import uniqueId from 'lodash/fp/uniqueId'; | ||
import differenceBy from 'lodash/fp/differenceBy'; | ||
import cx from 'classnames'; | ||
|
||
import minLngLatBounds from '../../utils/minLngLatBounds/minLngLatBounds'; | ||
import mapboxgl from '../../utils/mapboxgl/mapboxgl'; | ||
import InteractiveMarker from './InteractiveMarker'; | ||
import BaseMap from './BaseMap'; | ||
|
||
import css from './MarkableMap.css'; | ||
|
||
export default class MarkableMap extends Component { | ||
static propTypes = { | ||
markers: PropTypes.array, | ||
MarkerComponent: PropTypes.func.isRequired, | ||
autoFit: PropTypes.bool, | ||
}; | ||
|
||
static defaultProps = { | ||
markers: [], | ||
autoFit: false, | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
this.id = uniqueId('map_'); | ||
} | ||
|
||
state = { | ||
activeMarkerId: null, | ||
} | ||
|
||
componentDidMount() { | ||
const { markers, autoFit } = this.props; | ||
markers.forEach(this.renderMarker); | ||
if (autoFit) this.fitMarkers(); | ||
} | ||
|
||
componentDidUpdate(prevProps) { | ||
const { markers: prevMarkers } = prevProps; | ||
const { markers, autoFit } = this.props; | ||
|
||
const removedMarkers = differenceBy('id')(prevMarkers)(markers); | ||
removedMarkers.forEach(this.removeMarker); | ||
markers.forEach(this.renderMarker); | ||
|
||
const markersMoved = markers.some((marker) => { | ||
const prevMarker = prevMarkers.find(prev => prev.id === marker.id); | ||
return !prevMarker || !isEqual(prevMarker.lngLat, marker.lngLat); | ||
}); | ||
const markerChange = removedMarkers.length || markersMoved; | ||
|
||
if (autoFit && markerChange) this.fitMarkers(); | ||
} | ||
|
||
componentWillUnmount() { | ||
Object.keys(this.mapboxMarkers).forEach((id) => { | ||
this.removeMarker({ id }); | ||
}); | ||
} | ||
|
||
getMaboxGL = () => this.map.getMaboxGL(); | ||
|
||
handleMarkerClick = (e, id) => { | ||
const marker = this.mapboxMarkers[id]; | ||
const markerLngLat = marker.getLngLat(); | ||
const zoom = this.getMaboxGL().getZoom(); | ||
|
||
const nextLat = markerLngLat.lat + (80 / Math.pow(2, zoom)); | ||
const nextCenter = new mapboxgl.LngLat(markerLngLat.lng, nextLat).wrap(); | ||
|
||
this.getMaboxGL().easeTo({ center: nextCenter }); | ||
this.setState({ activeMarkerId: id }); | ||
} | ||
|
||
handleMapClick = ({ originalEvent }) => { | ||
if (originalEvent.target !== this.getMaboxGL().getCanvas()) return; | ||
this.setState({ activeMarkerId: null }); | ||
} | ||
|
||
mapboxMarkers = {}; | ||
|
||
removeMarker = ({ id }) => { | ||
const marker = this.mapboxMarkers[id]; | ||
unmountComponentAtNode(marker.getElement()); | ||
marker.remove(); | ||
delete this.mapboxMarkers[id]; | ||
if (this.state.activeMarkerId === id) this.setState({ activeMarkerId: null }); | ||
} | ||
|
||
fitMarkers = () => { | ||
const markers = Object.keys(this.mapboxMarkers).map(id => this.mapboxMarkers[id]); | ||
if (!markers.length) return; | ||
|
||
const destucturedMarkers = markers.map(marker => marker.getLngLat().toArray()); | ||
|
||
this.getMaboxGL().fitBounds( | ||
minLngLatBounds(destucturedMarkers), | ||
{ padding: 20, offset: [0, 20], maxZoom: 16 }, | ||
); | ||
} | ||
|
||
renderMarker = ({ lngLat, props, id }) => { | ||
const { activeMarkerId } = this.state; | ||
const { MarkerComponent } = this.props; | ||
|
||
let marker; | ||
if (this.mapboxMarkers[id]) { | ||
marker = this.mapboxMarkers[id]; | ||
marker.setLngLat(lngLat); | ||
} else { | ||
marker = new mapboxgl.Marker().setLngLat(lngLat).addTo(this.getMaboxGL()); | ||
this.mapboxMarkers[id] = marker; | ||
} | ||
|
||
const active = activeMarkerId === id; | ||
const element = marker.getElement(); | ||
element.className = cx(css.marker, active ? css.markerActive : null); | ||
|
||
renderSubtreeIntoContainer( | ||
this, | ||
<InteractiveMarker | ||
active={ active } | ||
id={ id } | ||
key={ `${this.id}-${id}-marker` } | ||
MarkerComponent={ MarkerComponent } | ||
onClick={ this.handleMarkerClick } | ||
props={ props } | ||
/>, | ||
element | ||
); | ||
|
||
return marker; | ||
} | ||
|
||
render() { | ||
const { markers: _markers, MarkerComponent: _MarkerComponent, ...rest } = this.props; | ||
return <BaseMap ref={ (c) => { this.map = c; } } { ...rest } onClick={ this.handleMapClick } />; | ||
} | ||
} |
Oops, something went wrong.