refactor timeline config to redux state

This commit is contained in:
Lachlan Kermode
2019-01-18 14:58:25 +00:00
parent 033ecdce8e
commit 3ebf668d27
5 changed files with 171 additions and 162 deletions

View File

@@ -1,27 +1,31 @@
import React from 'react'; import React from 'react'
import { Portal } from 'react-portal'; import { Portal } from 'react-portal'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import * as selectors from '../selectors' import * as selectors from '../selectors'
import hash from 'object-hash'; import hash from 'object-hash'
import 'leaflet'; import 'leaflet'
import { isNotNullNorUndefined } from '../js/utilities'; import { isNotNullNorUndefined } from '../js/utilities'
import Sites from './presentational/Map/Sites.jsx'; import Sites from './presentational/Map/Sites.jsx'
import Shapes from './presentational/Map/Shapes.jsx'; import Shapes from './presentational/Map/Shapes.jsx'
import Events from './presentational/Map/Events.jsx'; import Events from './presentational/Map/Events.jsx'
import SelectedEvents from './presentational/Map/SelectedEvents.jsx'; import SelectedEvents from './presentational/Map/SelectedEvents.jsx'
import Narratives from './presentational/Map/Narratives.jsx'; import Narratives from './presentational/Map/Narratives.jsx'
import DefsMarkers from './presentational/Map/DefsMarkers.jsx'; import DefsMarkers from './presentational/Map/DefsMarkers.jsx'
// NB: important constants for map, TODO: make statics
const supportedMapboxMap = ['streets', 'satellite']
const defaultToken = 'your_token'
class Map extends React.Component { class Map extends React.Component {
constructor() { constructor() {
super(); super()
this.projectPoint = this.projectPoint.bind(this) this.projectPoint = this.projectPoint.bind(this)
this.svgRef = React.createRef(); this.svgRef = React.createRef()
this.map = null; this.map = null
this.state = { this.state = {
mapTransformX: 0, mapTransformX: 0,
mapTransformY: 0 mapTransformY: 0
@@ -31,22 +35,23 @@ class Map extends React.Component {
componentDidMount(){ componentDidMount(){
if (this.map === null) { if (this.map === null) {
this.initializeMap(); this.initializeMap()
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
// Set appropriate zoom for narrative // Set appropriate zoom for narrative
if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds) const { bounds } = nextProps.app.map
&& nextProps.app.mapBounds !== null) { if (hash(bounds) !== hash(this.props.app.map.bounds)
this.map.fitBounds(nextProps.app.mapBounds); && bounds !== null) {
this.map.fitBounds(bounds)
} else { } else {
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
// Fly to first of events selected // Fly to first of events selected
const eventPoint = (nextProps.app.selected.length > 0) ? nextProps.app.selected[0] : null; const eventPoint = (nextProps.app.selected.length > 0) ? nextProps.app.selected[0] : null
if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) { if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) {
this.map.setView([eventPoint.latitude, eventPoint.longitude]); this.map.setView([eventPoint.latitude, eventPoint.longitude])
} }
} }
} }
@@ -56,51 +61,50 @@ class Map extends React.Component {
/** /**
* Creates a Leaflet map and a tilelayer for the map background * Creates a Leaflet map and a tilelayer for the map background
*/ */
const { map: mapConf } = this.props.app
const map = const map =
L.map(this.props.ui.dom.map) L.map(this.props.ui.dom.map)
.setView(this.props.app.mapAnchor, 14) .setView(mapConf.anchor, mapConf.startZoom)
.setMinZoom(7) .setMinZoom(mapConf.minZoom)
.setMaxZoom(18) .setMaxZoom(mapConf.maxZoom)
.setMaxBounds([[180, -180], [-180, 180]]) .setMaxBounds(mapConf.maxBounds)
let s; let s
if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== 'your_token') {
if ((supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1) && process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) {
s = L.tileLayer( s = L.tileLayer(
`http://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}` `http://a.tiles.mapbox.com/v4/mapbox.${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
); )
} else if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) {
s = L.tileLayer(
`http://a.tiles.mapbox.com/v4/${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
)
} else { } else {
// eslint-disable-next-line s = L.tileLayer(
alert(`No mapbox token specified in config. 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
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); s = s.addTo(map)
map.keyboard.disable(); map.keyboard.disable()
map.on('move zoomend viewreset moveend', () => this.alignLayers()); map.on('move zoomend viewreset moveend', () => this.alignLayers())
map.on('zoomstart', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.add('hide') }); 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'); }); map.on('zoomend', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.remove('hide') })
window.addEventListener('resize', () => { this.alignLayers(); }); window.addEventListener('resize', () => { this.alignLayers() })
this.map = map; this.map = map
} }
alignLayers() { alignLayers() {
const mapNode = document.querySelector('.leaflet-map-pane'); const mapNode = document.querySelector('.leaflet-map-pane')
if (mapNode === null) return { transformX: 0, transformY: 0 }; if (mapNode === null) return { transformX: 0, transformY: 0 }
// We'll get the transform of the leaflet container, // We'll get the transform of the leaflet container,
// which will let us offset the SVG by the same quantity // which will let us offset the SVG by the same quantity
const transform = window const transform = window
.getComputedStyle(mapNode) .getComputedStyle(mapNode)
.getPropertyValue('transform'); .getPropertyValue('transform')
// Offset with leaflet map transform boundaries // Offset with leaflet map transform boundaries
this.setState({ this.setState({
@@ -118,7 +122,7 @@ class Map extends React.Component {
} }
getClientDims() { getClientDims() {
const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect(); const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect()
return { return {
width: boundingClient.width, width: boundingClient.width,
@@ -127,8 +131,8 @@ class Map extends React.Component {
} }
renderTiles() { renderTiles() {
const pane = this.map.getPanes().overlayPane; const pane = this.map.getPanes().overlayPane
const { width, height } = this.getClientDims(); const { width, height } = this.getClientDims()
return ( return (
<Portal node={pane}> <Portal node={pane}>
@@ -141,7 +145,7 @@ class Map extends React.Component {
> >
</svg> </svg>
</Portal> </Portal>
); )
} }
renderSites() { renderSites() {
@@ -151,7 +155,7 @@ class Map extends React.Component {
projectPoint={this.projectPoint} projectPoint={this.projectPoint}
isEnabled={this.props.app.views.sites} isEnabled={this.props.app.views.sites}
/> />
); )
} }
renderShapes() { renderShapes() {
@@ -176,7 +180,7 @@ class Map extends React.Component {
onSelect={this.props.methods.onSelect} onSelect={this.props.methods.onSelect}
onSelectNarrative={this.props.methods.onSelectNarrative} onSelectNarrative={this.props.methods.onSelectNarrative}
/> />
); )
} }
/** /**
@@ -209,7 +213,7 @@ class Map extends React.Component {
onSelectNarrative={this.props.methods.onSelectNarrative} onSelectNarrative={this.props.methods.onSelectNarrative}
getCategoryColor={this.props.methods.getCategoryColor} getCategoryColor={this.props.methods.getCategoryColor}
/> />
); )
} }
renderSelected() { renderSelected() {
@@ -219,7 +223,7 @@ class Map extends React.Component {
selected={this.props.app.selected} selected={this.props.app.selected}
projectPoint={this.projectPoint} projectPoint={this.projectPoint}
/> />
); )
} }
@@ -234,7 +238,7 @@ class Map extends React.Component {
render() { render() {
const { isShowingSites } = this.props.app.flags const { isShowingSites } = this.props.app.flags
const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper'; const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper'
const innerMap = !!this.map ? ( const innerMap = !!this.map ? (
<React.Fragment> <React.Fragment>
{this.renderTiles()} {this.renderTiles()}
@@ -252,7 +256,7 @@ class Map extends React.Component {
<div id={this.props.ui.dom.map} /> <div id={this.props.ui.dom.map} />
{innerMap} {innerMap}
</div> </div>
); )
} }
} }
@@ -269,14 +273,14 @@ function mapStateToProps(state) {
views: state.app.filters.views, views: state.app.filters.views,
selected: state.app.selected, selected: state.app.selected,
highlighted: state.app.highlighted, highlighted: state.app.highlighted,
mapAnchor: state.app.mapAnchor, map: state.app.map,
mapBounds: state.app.filters.mapBounds,
narrative: state.app.narrative, narrative: state.app.narrative,
flags: { flags: {
isShowingSites: state.app.flags.isShowingSites isShowingSites: state.app.flags.isShowingSites
} }
}, },
ui: { ui: {
tiles: state.ui.tiles,
dom: state.ui.dom, dom: state.ui.dom,
narratives: state.ui.style.narratives, narratives: state.ui.style.narratives,
shapes: state.ui.style.shapes shapes: state.ui.style.shapes

View File

@@ -1,90 +1,90 @@
import React from 'react'; import React from 'react'
import { connect } from 'react-redux'; import { connect } from 'react-redux'
import * as selectors from '../selectors'; import * as selectors from '../selectors'
import hash from 'object-hash'; import hash from 'object-hash'
import copy from '../js/data/copy.json'; import copy from '../js/data/copy.json'
import { formatterWithYear, parseDate } from '../js/utilities'; import { formatterWithYear, parseDate } from '../js/utilities'
import TimelineHeader from './presentational/Timeline/Header'; import Header from './presentational/Timeline/Header'
import TimelineAxis from './TimelineAxis.jsx'; import Axis from './TimelineAxis.jsx'
import TimelineClip from './presentational/Timeline/Clip'; import Clip from './presentational/Timeline/Clip'
import TimelineHandles from './presentational/Timeline/Handles.js'; import Handles from './presentational/Timeline/Handles.js'
import TimelineZoomControls from './presentational/Timeline/ZoomControls.js'; import ZoomControls from './presentational/Timeline/ZoomControls.js'
import TimelineLabels from './presentational/Timeline/Labels.js'; import Labels from './presentational/Timeline/Labels.js'
import TimelineMarkers from './presentational/Timeline/Markers.js' import Markers from './presentational/Timeline/Markers.js'
import TimelineEvents from './presentational/Timeline/Events.js'; import Events from './presentational/Timeline/Events.js'
import TimelineCategories from './TimelineCategories.jsx'; import Categories from './TimelineCategories.jsx'
class Timeline extends React.Component { class Timeline extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props)
this.styleDatetime = this.styleDatetime.bind(this) this.styleDatetime = this.styleDatetime.bind(this)
this.getDatetimeX = this.getDatetimeX.bind(this) this.getDatetimeX = this.getDatetimeX.bind(this)
this.onApplyZoom = this.onApplyZoom.bind(this) this.onApplyZoom = this.onApplyZoom.bind(this)
this.svgRef = React.createRef() this.svgRef = React.createRef()
this.state = { this.state = {
isFolded: false, isFolded: false,
dims: props.app.dims, dims: props.app.timeline.dimensions,
scaleX: null, scaleX: null,
scaleY: null, scaleY: null,
timerange: [null, null], timerange: [null, null],
dragPos0: null, dragPos0: null,
transitionDuration: 300 transitionDuration: 300
}; }
} }
componentDidMount() { componentDidMount() {
this.computeDims(); this.computeDims()
this.addEventListeners(); this.addEventListeners()
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (hash(nextProps) !== hash(this.props)) { if (hash(nextProps) !== hash(this.props)) {
this.setState({ this.setState({
timerange: nextProps.app.timerange, timerange: nextProps.app.timeline.range,
scaleX: this.makeScaleX() scaleX: this.makeScaleX()
}); })
} }
if (hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) { if (hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) {
this.setState({ this.setState({
scaleY: this.makeScaleY(nextProps.domain.categories) scaleY: this.makeScaleY(nextProps.domain.categories)
}); })
} }
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
if (!!nextProps.app.selected && nextProps.app.selected.length > 0) { if (!!nextProps.app.selected && nextProps.app.selected.length > 0) {
this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp)); this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp))
} }
} }
} }
addEventListeners() { addEventListeners() {
window.addEventListener('resize', () => { this.computeDims(); }); window.addEventListener('resize', () => { this.computeDims() })
let element = document.querySelector('.timeline-wrapper'); let element = document.querySelector('.timeline-wrapper')
element.addEventListener("transitionend", (event) => { element.addEventListener("transitionend", (event) => {
this.computeDims(); this.computeDims()
}); })
} }
makeScaleX() { makeScaleX() {
return d3.scaleTime() return d3.scaleTime()
.domain(this.state.timerange) .domain(this.state.timerange)
.range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls]); .range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls])
} }
makeScaleY(categories) { makeScaleY(categories) {
const tickHeight = 15; const tickHeight = 15
const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2); const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2)
return d3.scaleOrdinal() return d3.scaleOrdinal()
.domain(categories) .domain(categories)
.range(catsYpos); .range(catsYpos)
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevState.timerange !== this.state.timerange) { if (prevState.timerange !== this.state.timerange) {
this.setState({ scaleX: this.makeScaleX() }); this.setState({ scaleX: this.makeScaleX() })
} }
} }
@@ -93,20 +93,20 @@ class Timeline extends React.Component {
*/ */
getTimeScaleExtent() { getTimeScaleExtent() {
if (!this.state.scaleX) return 0 if (!this.state.scaleX) return 0
const timeDomain = this.state.scaleX.domain(); const timeDomain = this.state.scaleX.domain()
return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000; return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000
} }
onClickArrow() { onClickArrow() {
this.setState((prevState, props) => { this.setState((prevState, props) => {
return {isFolded: !prevState.isFolded}; return {isFolded: !prevState.isFolded}
}); })
} }
computeDims() { computeDims() {
const dom = this.props.ui.dom.timeline; const dom = this.props.ui.dom.timeline
if (document.querySelector(`#${dom}`) !== null) { if (document.querySelector(`#${dom}`) !== null) {
const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect(); const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect()
this.setState({ this.setState({
dims: { dims: {
@@ -117,7 +117,7 @@ class Timeline extends React.Component {
() => { () => {
this.setState({ scaleX: this.makeScaleX() this.setState({ scaleX: this.makeScaleX()
}) })
}); })
} }
} }
@@ -126,34 +126,34 @@ class Timeline extends React.Component {
* @param {String} direction: 'forward' / 'backwards' * @param {String} direction: 'forward' / 'backwards'
*/ */
onMoveTime(direction) { onMoveTime(direction) {
this.props.methods.onSelect(); this.props.methods.onSelect()
const extent = this.getTimeScaleExtent(); const extent = this.getTimeScaleExtent()
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2); const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
// if forward // if forward
let domain0 = newCentralTime; let domain0 = newCentralTime
let domainF = d3.timeMinute.offset(newCentralTime, extent); let domainF = d3.timeMinute.offset(newCentralTime, extent)
// if backwards // if backwards
if (direction === 'backwards') { if (direction === 'backwards') {
domain0 = d3.timeMinute.offset(newCentralTime, -extent); domain0 = d3.timeMinute.offset(newCentralTime, -extent)
domainF = newCentralTime; domainF = newCentralTime
} }
this.setState({ timerange: [domain0, domainF] }, () => { this.setState({ timerange: [domain0, domainF] }, () => {
this.props.methods.onUpdateTimerange(this.state.timerange); this.props.methods.onUpdateTimerange(this.state.timerange)
}); })
} }
onCenterTime(newCentralTime) { onCenterTime(newCentralTime) {
const extent = this.getTimeScaleExtent(); const extent = this.getTimeScaleExtent()
const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2); const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2)
const domainF = d3.timeMinute.offset(newCentralTime, +extent/2); const domainF = d3.timeMinute.offset(newCentralTime, +extent/2)
this.setState({ timerange: [domain0, domainF] }, () => { this.setState({ timerange: [domain0, domainF] }, () => {
this.props.methods.onUpdateTimerange(this.state.timerange); this.props.methods.onUpdateTimerange(this.state.timerange)
}); })
} }
/** /**
@@ -162,7 +162,7 @@ class Timeline extends React.Component {
* Used for updates in the middle of a transition, for performance purposes * Used for updates in the middle of a transition, for performance purposes
*/ */
onSoftTimeRangeUpdate(timerange) { onSoftTimeRangeUpdate(timerange) {
this.setState({ timerange }); this.setState({ timerange })
} }
/** /**
@@ -170,54 +170,55 @@ class Timeline extends React.Component {
* @param {object} zoom: zoom level from zoomLevels * @param {object} zoom: zoom level from zoomLevels
*/ */
onApplyZoom(zoom) { onApplyZoom(zoom) {
const extent = this.getTimeScaleExtent(); const extent = this.getTimeScaleExtent()
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2); const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
this.setState({ timerange: [ this.setState({ timerange: [
d3.timeMinute.offset(newCentralTime, -zoom.duration / 2), d3.timeMinute.offset(newCentralTime, -zoom.duration / 2),
d3.timeMinute.offset(newCentralTime, zoom.duration / 2) d3.timeMinute.offset(newCentralTime, zoom.duration / 2)
]}, () => { ]}, () => {
this.props.methods.onUpdateTimerange(this.state.timerange); this.props.methods.onUpdateTimerange(this.state.timerange)
}); })
} }
toggleTransition(isTransition) { toggleTransition(isTransition) {
this.setState({ transitionDuration: (isTransition) ? 300 : 0 }); this.setState({ transitionDuration: (isTransition) ? 300 : 0 })
} }
/* /*
* Setup drag behavior * Setup drag behavior
*/ */
onDragStart() { onDragStart() {
d3.event.sourceEvent.stopPropagation(); d3.event.sourceEvent.stopPropagation()
this.setState({ this.setState({
dragPos0: d3.event.x dragPos0: d3.event.x
}, () => { }, () => {
this.toggleTransition(false); this.toggleTransition(false)
}); })
} }
/* /*
* Drag and update * Drag and update
*/ */
onDrag() { onDrag() {
const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime(); const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime()
const dragNow = this.state.scaleX.invert(d3.event.x).getTime(); const dragNow = this.state.scaleX.invert(d3.event.x).getTime()
const timeShift = (drag0 - dragNow) / 1000; const timeShift = (drag0 - dragNow) / 1000
const newDomain0 = d3.timeSecond.offset(this.props.app.timerange[0], timeShift); const { range } = this.props.app.timeline
const newDomainF = d3.timeSecond.offset(this.props.app.timerange[1], timeShift); const newDomain0 = d3.timeSecond.offset(range[0], timeShift)
const newDomainF = d3.timeSecond.offset(range[1], timeShift)
// Updates components without updating timerange // Updates components without updating timerange
this.onSoftTimeRangeUpdate([newDomain0, newDomainF]); this.onSoftTimeRangeUpdate([newDomain0, newDomainF])
} }
/** /**
* Stop dragging and update data * Stop dragging and update data
*/ */
onDragEnd() { onDragEnd() {
this.toggleTransition(true); this.toggleTransition(true)
this.props.methods.onUpdateTimerange(this.state.timerange); this.props.methods.onUpdateTimerange(this.state.timerange)
} }
getDatetimeX(dt) { getDatetimeX(dt) {
@@ -243,17 +244,17 @@ class Timeline extends React.Component {
render() { render() {
const { isNarrative, app, ui } = this.props const { isNarrative, app, ui } = this.props
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`; let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`
classes += (app.narrative !== null) ? ' narrative-mode' : ''; classes += (app.narrative !== null) ? ' narrative-mode' : ''
const dims = this.state.dims; const { dims } = this.state
return ( return (
<div className={classes}> <div className={classes}>
<TimelineHeader <Header
title={copy[this.props.app.language].timeline.info} title={copy[this.props.app.language].timeline.info}
date0={formatterWithYear(this.state.timerange[0])} date0={formatterWithYear(this.state.timerange[0])}
date1={formatterWithYear(this.state.timerange[1])} date1={formatterWithYear(this.state.timerange[1])}
onClick={() => { this.onClickArrow(); }} onClick={() => { this.onClickArrow() }}
hideInfo={isNarrative} hideInfo={isNarrative}
/> />
<div className="timeline-content"> <div className="timeline-content">
@@ -263,43 +264,43 @@ class Timeline extends React.Component {
width={dims.width} width={dims.width}
height={dims.height} height={dims.height}
> >
<TimelineClip <Clip
dims={dims} dims={dims}
/> />
<TimelineAxis <Axis
dims={dims} dims={dims}
timerange={this.props.app.timerange} timerange={this.props.app.timerange}
transitionDuration={this.state.transitionDuration} transitionDuration={this.state.transitionDuration}
scaleX={this.state.scaleX} scaleX={this.state.scaleX}
/> />
<TimelineCategories <Categories
dims={dims} dims={dims}
onDragStart={() => { this.onDragStart() }} onDragStart={() => { this.onDragStart() }}
onDrag={() => { this.onDrag() }} onDrag={() => { this.onDrag() }}
onDragEnd={() => { this.onDragEnd() }} onDragEnd={() => { this.onDragEnd() }}
categories={this.props.domain.categories} categories={this.props.domain.categories}
/> />
<TimelineHandles <Handles
dims={dims} dims={dims}
onMoveTime={(dir) => { this.onMoveTime(dir) }} onMoveTime={(dir) => { this.onMoveTime(dir) }}
/> />
<TimelineZoomControls <ZoomControls
extent={this.getTimeScaleExtent()} extent={this.getTimeScaleExtent()}
zoomLevels={this.props.app.zoomLevels} zoomLevels={this.props.app.timeline.zoomLevels}
dims={dims} dims={dims}
onApplyZoom={this.onApplyZoom} onApplyZoom={this.onApplyZoom}
/> />
<TimelineLabels <Labels
dims={dims} dims={dims}
timelabels={this.state.timerange} timelabels={this.state.timerange}
/> />
<TimelineMarkers <Markers
selected={this.props.app.selected} selected={this.props.app.selected}
getEventX={this.getDatetimeX} getEventX={this.getDatetimeX}
getCategoryY={this.state.scaleY} getCategoryY={this.state.scaleY}
transitionDuration={this.state.transitionDuration} transitionDuration={this.state.transitionDuration}
/> />
<TimelineEvents <Events
datetimes={this.props.domain.datetimes} datetimes={this.props.domain.datetimes}
styleDatetime={this.styleDatetime} styleDatetime={this.styleDatetime}
narrative={this.props.app.narrative} narrative={this.props.app.narrative}
@@ -313,7 +314,7 @@ class Timeline extends React.Component {
</div> </div>
</div> </div>
</div> </div>
); )
} }
} }
@@ -326,11 +327,9 @@ function mapStateToProps(state) {
narratives: state.domain.narratives narratives: state.domain.narratives
}, },
app: { app: {
timerange: selectors.getTimeRange(state),
dims: state.app.timeline.dimensions,
selected: state.app.selected, selected: state.app.selected,
language: state.app.language, language: state.app.language,
zoomLevels: state.app.timeline.zoomLevels, timeline: state.app.timeline,
narrative: state.app.narrative narrative: state.app.narrative
}, },
ui: { ui: {
@@ -339,4 +338,4 @@ function mapStateToProps(state) {
} }
} }
export default connect(mapStateToProps)(Timeline); export default connect(mapStateToProps)(Timeline)

View File

@@ -36,8 +36,8 @@ function updateSelected(appState, action) {
} }
function updateNarrative(appState, action) { function updateNarrative(appState, action) {
let minTime = appState.filters.timerange[0] let minTime = appState.timeline.range[0]
let maxTime = appState.filters.timerange[1] let maxTime = appState.timeline.range[1]
let cornerBound0 = [180, 180] let cornerBound0 = [180, 180]
let cornerBound1 = [-180, -180] let cornerBound1 = [-180, -180]

View File

@@ -25,7 +25,7 @@ export const getNotifications = state => state.domain.notifications
export const getTagTree = state => state.domain.tags export const getTagTree = state => state.domain.tags
export const getTagsFilter = state => state.app.filters.tags export const getTagsFilter = state => state.app.filters.tags
export const getCategoriesFilter = state => state.app.filters.categories export const getCategoriesFilter = state => state.app.filters.categories
export const getTimeRange = state => state.app.filters.timerange export const getTimeRange = state => state.app.timeline.range
/** /**

View File

@@ -39,23 +39,24 @@ const initial = {
current: null current: null
}, },
filters: { filters: {
timerange: [
new Date(2013, 2, 23, 12),
new Date(2016, 2, 23, 12)
],
mapBounds: null,
tags: [], tags: [],
categories: [], categories: [],
views: { views: {
events: true, events: true,
coevents: false,
routes: false, routes: false,
sites: true sites: true
}, },
}, },
isMobile: (/Mobi/.test(navigator.userAgent)), isMobile: (/Mobi/.test(navigator.userAgent)),
language: 'en-US', language: 'en-US',
mapAnchor: [31.356397, 34.784818], map: {
anchor: [31.356397, 34.784818],
startZoom: 10,
minZoom: 7,
maxZoom: 18,
bounds: null,
maxBounds: [[180, -180], [-180, 180]]
},
timeline: { timeline: {
dimensions: { dimensions: {
height: 140, height: 140,
@@ -66,6 +67,10 @@ const initial = {
margin_top: 20, margin_top: 20,
trackHeight: 80 trackHeight: 80
}, },
range: [
new Date(2013, 2, 23, 12),
new Date(2016, 2, 23, 12)
],
zoomLevels: [ zoomLevels: [
{ label: '3 years', duration: 1576800 }, { label: '3 years', duration: 1576800 },
{ label: '3 months', duration: 129600 }, { label: '3 months', duration: 129600 },
@@ -92,6 +97,7 @@ const initial = {
* as well as dom elements to attach SVG * as well as dom elements to attach SVG
*/ */
ui: { ui: {
tiles: 'openstreetmap', // ['openstreetmap', 'streets', 'satellite']
style: { style: {
categories: { categories: {
default: '#f3de2c', default: '#f3de2c',
@@ -127,7 +133,7 @@ if (process.env.store) {
} }
// NB: config.js dates get implicitly converted to strings in mergeDeepLeft // NB: config.js dates get implicitly converted to strings in mergeDeepLeft
appStore.app.filters.timerange[0] = new Date(appStore.app.filters.timerange[0]) appStore.app.timeline.range[0] = new Date(appStore.app.timeline.range[0])
appStore.app.filters.timerange[1] = new Date(appStore.app.filters.timerange[1]) appStore.app.timeline.range[1] = new Date(appStore.app.timeline.range[1])
export default appStore export default appStore