Files
ukraine-timemap/src/components/Map.jsx
2018-12-19 15:08:51 +01:00

229 lines
6.1 KiB
JavaScript

import React from 'react';
import { Portal } from 'react-portal';
import { connect } from 'react-redux'
import * as selectors from '../selectors'
import hash from 'object-hash';
import MapLogic from '../js/map/map.js'
import MapSites from './MapSites.jsx';
import MapEvents from './MapEvents.jsx';
import MapNarratives from './MapNarratives.jsx';
import MapDefsMarkers from './MapDefsMarkers.jsx';
class Map extends React.Component {
constructor() {
super();
this.svgRef = React.createRef();
this.map = null;
this.state = {
mapTransformX: 0,
mapTransformY: 0
}
}
componentDidMount(){
if (this.map === null) {
this.initializeMap();
}
}
componentWillReceiveProps(nextProps) {
if (hash(nextProps.app) !== hash(this.props.app)) {
this.mapLogic.update(nextProps.app)
}
}
initializeMap() {
/**
* Creates a Leaflet map and a tilelayer for the map background
*/
const map =
L.map(this.props.mapId)
.setView(this.props.app.mapAnchor, 14)
.setMinZoom(10)
.setMaxZoom(18)
.setMaxBounds([[180, -180], [-180, 180]])
let s;
if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== 'your_token') {
s = L.tileLayer(
`http://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
);
} else {
// eslint-disable-next-line
alert(`No mapbox token specified in config.
Timemap does not currently support any other tiling layer,
so you will need to sign up for one at:
https://www.mapbox.com/
Stop and start the development process in terminal after you have added your token to config.js`
)
return
}
s = s.addTo(map);
map.keyboard.disable();
map.on("move", () => this.alignLayers());
map.on("zoomend viewreset moveend", () => this.alignLayers());
map.on('zoomstart', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.add('hide') });
map.on('zoomend', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.remove('hide'); });
this.addResizeListener();
this.mapLogic = new MapLogic(map, this.svgRef.current, this.props.app, this.props.ui);
this.mapLogic.update(this.props.app);
this.map = map;
}
addResizeListener() {
window.addEventListener('resize', () => {
this.alignLayers();
});
}
alignLayers() {
const mapNode = document.querySelector('.leaflet-map-pane');
if (mapNode === null) return { transformX: 0, transformY: 0 };
// We'll get the transform of the leaflet container,
// which will let us offset the SVG by the same quantity
const transform = window
.getComputedStyle(mapNode)
.getPropertyValue('transform');
// Offset with leaflet map transform boundaries
this.setState({
mapTransformX: +transform.split(',')[4],
mapTransformY: +transform.split(',')[5].split(')')[0]
})
}
getClientDims() {
const boundingClient = document.querySelector(`#${this.props.mapId}`).getBoundingClientRect();
return {
width: boundingClient.width,
height: boundingClient.height
}
}
renderSVG() {
if (this.map === null) return '';
const pane = this.map.getPanes().overlayPane;
const { width, height } = this.getClientDims();
return (
<Portal node={pane}>
<svg
ref={this.svgRef}
width={width}
height={height}
style={{ transform: `translate3d(${-this.state.mapTransformX}px, ${-this.state.mapTransformY}px, 0)`}}
className='leaflet-svg'
>
</svg>
</Portal>
);
}
renderSites() {
return (
<MapSites
sites={this.props.domain.sites}
map={this.map}
mapTransformX={this.state.mapTransformX}
mapTransformY={this.state.mapTransformY}
isEnabled={this.props.app.views.sites}
/>
);
}
renderNarratives() {
return (
<MapNarratives
svg={this.svgRef.current}
narratives={this.props.domain.narratives}
map={this.map}
mapTransformX={this.state.mapTransformX}
mapTransformY={this.state.mapTransformY}
narrative={this.props.app.narrative}
narrativeProps={this.props.ui.narratives}
onSelect={this.props.methods.onSelect}
onSelectNarrative={this.props.methods.onSelectNarrative}
/>
);
}
renderEvents() {
return (
<MapEvents
svg={this.svgRef.current}
locations={this.props.domain.locations}
categories={this.props.domain.categories}
map={this.map}
mapTransformX={this.state.mapTransformX}
mapTransformY={this.state.mapTransformY}
narrative={this.props.app.narrative}
onSelect={this.props.methods.onSelect}
onSelectNarrative={this.props.methods.onSelectNarrative}
getCategoryColor={this.props.methods.getCategoryColor}
/>
);
}
renderMarkers() {
return (
<Portal node={this.svgRef.current}>
<MapDefsMarkers />
</Portal>
)
}
render() {
const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper';
return (
<div className={classes}>
<div id={this.props.mapId} />
{this.renderSVG()}
{(this.map !== null) ? this.renderMarkers() : ''}
{(this.map !== null) ? this.renderSites() : ''}
{(this.map !== null) ? this.renderEvents() : ''}
{(this.map !== null) ? this.renderNarratives() : ''}
</div>
);
}
}
function mapStateToProps(state) {
return {
domain: {
locations: selectors.selectLocations(state),
narratives: selectors.selectNarratives(state),
categories: selectors.selectCategories(state),
sites: selectors.getSites(state)
},
app: {
views: state.app.filters.views,
selected: state.app.selected,
highlighted: state.app.highlighted,
mapAnchor: state.app.mapAnchor,
narrative: state.app.narrative
},
ui: {
dom: state.ui.dom,
narratives: state.ui.style.narratives
}
}
}
export default connect(mapStateToProps)(Map)