diff --git a/.gitignore b/.gitignore
index 4ce2e5c..3b88e33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@ build/
node_modules/
config.js
dev.config.js
+
+src/\.DS_Store
diff --git a/example.config.js b/example.config.js
index ee5b1c6..852b944 100644
--- a/example.config.js
+++ b/example.config.js
@@ -7,7 +7,7 @@ module.exports = {
SOURCES_EXT: '/api/example/export_sources/deepids',
TAGS_EXT: '/api/example/export_tags/tree',
SITES_EXT: '/api/example/export_sites/rows',
- MAP_ANCHOR: [31.356397, 34.784818],
+ SHAPES_EXT: '/api/example/export_shapes/columns',
INCOMING_DATETIME_FORMAT: '%m/%d/%YT%H:%M',
MAPBOX_TOKEN: 'pk.EXAMPLE_MAPBOX_TOKEN',
features: {
@@ -15,7 +15,26 @@ module.exports = {
USE_SEARCH: false,
USE_SITES: true,
USE_SOURCES: true,
+ USE_SHAPES: true,
CATEGORIES_AS_TAGS: true
+ },
+ store: {
+ app: {
+ mapAnchor: [31.356397, 34.784818],
+ filters: {
+ // timerange: [
+ // new Date(2015, 7, 9),
+ // new Date(2015, 10, 6, 23)
+ // ]
+ }
+ },
+ ui: {
+ style: {
+ categories: {},
+ shapes: {},
+ narratives: {}
+ }
+ }
}
}
diff --git a/src/actions/index.js b/src/actions/index.js
index cad3fa1..c2f6041 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -1,12 +1,14 @@
import { urlFromEnv } from '../js/utilities'
-const EVENT_DATA_URL = urlFromEnv('EVENT_EXT');
-const CATEGORY_URL = urlFromEnv('CATEGORY_EXT');
-const TAGS_URL = urlFromEnv('TAGS_EXT');
-const SOURCES_URL = urlFromEnv('SOURCES_EXT');
-const NARRATIVE_URL = urlFromEnv('NARRATIVE_EXT');
-const SITES_URL = urlFromEnv('SITES_EXT');
-const eventUrlMap = (event) => `${process.env.SERVER_ROOT}${process.env.EVENT_DESC_ROOT}/${(event.id) ? event.id : event}`;
+// TODO: relegate these URLs entirely to environment variables
+const EVENT_DATA_URL = urlFromEnv('EVENT_EXT')
+const CATEGORY_URL = urlFromEnv('CATEGORY_EXT')
+const TAGS_URL = urlFromEnv('TAGS_EXT')
+const SOURCES_URL = urlFromEnv('SOURCES_EXT')
+const NARRATIVE_URL = urlFromEnv('NARRATIVE_EXT')
+const SITES_URL = urlFromEnv('SITES_EXT')
+const SHAPES_URL = urlFromEnv('SHAPES_EXT')
+const eventUrlMap = (event) => `${process.env.SERVER_ROOT}${process.env.EVENT_DESC_ROOT}/${(event.id) ? event.id : event}`
const domainMsg = (domainType) => `Something went wrong fetching ${domainType}. Check the URL or try disabling them in the config file.`
@@ -67,13 +69,21 @@ export function fetchDomain () {
}
}
+ let shapesPromise = Promise.resolve([])
+ if (process.env.features.USE_SHAPES) {
+ shapesPromise = fetch(SHAPES_URL)
+ .then(response => response.json())
+ .catch(() => handleError(domainMsg('shapes')))
+ }
+
return Promise.all([
eventPromise,
catPromise,
narPromise,
sitesPromise,
tagsPromise,
- sourcesPromise
+ sourcesPromise,
+ shapesPromise
])
.then(response => {
const result = {
@@ -83,6 +93,7 @@ export function fetchDomain () {
sites: response[3],
tags: response[4],
sources: response[5],
+ shapes: response[6],
notifications
}
if (Object.values(result).some(resp => resp.hasOwnProperty('error'))) {
@@ -96,7 +107,7 @@ export function fetchDomain () {
// TODO: handle this appropriately in React hierarchy
alert(err.message)
})
- };
+ }
}
export const FETCH_ERROR = 'FETCH_ERROR'
@@ -189,7 +200,7 @@ export function updateTimeRange(timerange) {
}
}
-export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE';
+export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE'
export function updateNarrative(narrative) {
return {
type: UPDATE_NARRATIVE,
@@ -197,14 +208,14 @@ export function updateNarrative(narrative) {
}
}
-export const INCREMENT_NARRATIVE_CURRENT = 'INCREMENT_NARRATIVE_CURRENT';
+export const INCREMENT_NARRATIVE_CURRENT = 'INCREMENT_NARRATIVE_CURRENT'
export function incrementNarrativeCurrent() {
return {
type: INCREMENT_NARRATIVE_CURRENT
}
}
-export const DECREMENT_NARRATIVE_CURRENT = 'DECREMENT_NARRATIVE_CURRENT';
+export const DECREMENT_NARRATIVE_CURRENT = 'DECREMENT_NARRATIVE_CURRENT'
export function decrementNarrativeCurrent() {
return {
type: DECREMENT_NARRATIVE_CURRENT
@@ -249,7 +260,7 @@ export function toggleFetchingSources() {
}
}
-export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE';
+export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE'
export function toggleLanguage(language) {
return {
type: TOGGLE_LANGUAGE,
@@ -257,21 +268,21 @@ export function toggleLanguage(language) {
}
}
-export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR';
+export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR'
export function closeToolbar() {
return {
type: CLOSE_TOOLBAR
}
}
-export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP';
+export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP'
export function toggleInfoPopup() {
return {
type: TOGGLE_INFOPOPUP
}
}
-export const TOGGLE_MAPVIEW = 'TOGGLE_MAPVIEW';
+export const TOGGLE_MAPVIEW = 'TOGGLE_MAPVIEW'
export function toggleMapView(layer) {
return {
type: TOGGLE_MAPVIEW,
diff --git a/src/components/.DS_Store b/src/components/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/src/components/.DS_Store differ
diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 2e996e9..b77e31c 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -7,14 +7,14 @@ import {
import React from 'react'
import Spinner from './presentational/Spinner'
-import CardTimestamp from './presentational/CardTimestamp'
-import CardLocation from './presentational/CardLocation'
-import CardCaret from './presentational/CardCaret'
-import CardTags from './presentational/CardTags'
-import CardSummary from './presentational/CardSummary'
-import CardSource from './presentational/CardSource'
-import CardCategory from './presentational/CardCategory'
-import CardNarrative from './presentational/CardNarrative'
+import CardTimestamp from './presentational/Card/Timestamp'
+import CardLocation from './presentational/Card/Location'
+import CardCaret from './presentational/Card/Caret'
+import CardTags from './presentational/Card/Tags'
+import CardSummary from './presentational/Card/Summary'
+import CardSource from './presentational/Card/Source'
+import CardCategory from './presentational/Card/Category'
+import CardNarrative from './presentational/Card/Narrative'
class Card extends React.Component {
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
index 5817f83..7465bb7 100644
--- a/src/components/Dashboard.jsx
+++ b/src/components/Dashboard.jsx
@@ -9,7 +9,7 @@ import LoadingOverlay from './presentational/LoadingOverlay'
import Map from './Map.jsx'
import Toolbar from './Toolbar.jsx'
import CardStack from './CardStack.jsx'
-import NarrativeControls from './presentational/NarrativeControls.js'
+import NarrativeControls from './presentational/Narrative/Controls.js'
import InfoPopUp from './InfoPopup.jsx'
import Timeline from './Timeline.jsx'
import Notification from './Notification.jsx'
diff --git a/src/components/Map.jsx b/src/components/Map.jsx
index 8b0dd40..d5c8d85 100644
--- a/src/components/Map.jsx
+++ b/src/components/Map.jsx
@@ -1,26 +1,31 @@
-import React from 'react';
-import { Portal } from 'react-portal';
+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 'leaflet';
+import hash from 'object-hash'
+import 'leaflet'
-import { isNotNullNorUndefined } from '../js/utilities';
+import { isNotNullNorUndefined } from '../js/utilities'
-import MapSites from './MapSites.jsx';
-import MapEvents from './MapEvents.jsx';
-import MapSelectedEvents from './MapSelectedEvents.jsx';
-import MapNarratives from './MapNarratives.jsx';
-import MapDefsMarkers from './MapDefsMarkers.jsx';
+import Sites from './presentational/Map/Sites.jsx'
+import Shapes from './presentational/Map/Shapes.jsx'
+import Events from './presentational/Map/Events.jsx'
+import SelectedEvents from './presentational/Map/SelectedEvents.jsx'
+import Narratives from './presentational/Map/Narratives.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 {
-
constructor() {
- super();
- this.svgRef = React.createRef();
- this.map = null;
+ super()
+ this.projectPoint = this.projectPoint.bind(this)
+ this.svgRef = React.createRef()
+ this.map = null
this.state = {
mapTransformX: 0,
mapTransformY: 0
@@ -30,76 +35,76 @@ class Map extends React.Component {
componentDidMount(){
if (this.map === null) {
- this.initializeMap();
+ this.initializeMap()
}
}
componentWillReceiveProps(nextProps) {
- // Set appropriate zoom for narrative
- if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds)
- && nextProps.app.mapBounds !== null) {
- this.map.fitBounds(nextProps.app.mapBounds);
+ // Set appropriate zoom for narrative
+ const { bounds } = nextProps.app.map
+ if (hash(bounds) !== hash(this.props.app.map.bounds)
+ && bounds !== null) {
+ this.map.fitBounds(bounds)
} else {
if (hash(nextProps.app.selected) !== hash(this.props.app.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) {
- this.map.setView([eventPoint.latitude, eventPoint.longitude]);
+ this.map.setView([eventPoint.latitude, eventPoint.longitude])
}
- }
+ }
}
- }
+ }
initializeMap() {
/**
* Creates a Leaflet map and a tilelayer for the map background
*/
+ const { map: mapConf } = this.props.app
const map =
L.map(this.props.ui.dom.map)
- .setView(this.props.app.mapAnchor, 14)
- .setMinZoom(7)
- .setMaxZoom(18)
- .setMaxBounds([[180, -180], [-180, 180]])
+ .setView(mapConf.anchor, mapConf.startZoom)
+ .setMinZoom(mapConf.minZoom)
+ .setMaxZoom(mapConf.maxZoom)
+ .setMaxBounds(mapConf.maxBounds)
- let s;
- if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== 'your_token') {
+ let s
+
+ if ((supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1) && process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) {
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/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?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 = L.tileLayer(
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
+ )
}
- s = s.addTo(map);
+ s = s.addTo(map)
- map.keyboard.disable();
+ map.keyboard.disable()
- map.on('move 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'); });
- window.addEventListener('resize', () => { 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('zoomend', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.remove('hide') })
+ window.addEventListener('resize', () => { this.alignLayers() })
- this.map = map;
+ this.map = map
}
alignLayers() {
- const mapNode = document.querySelector('.leaflet-map-pane');
- if (mapNode === null) return { transformX: 0, transformY: 0 };
+ 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');
+ .getPropertyValue('transform')
// Offset with leaflet map transform boundaries
this.setState({
@@ -108,8 +113,16 @@ class Map extends React.Component {
})
}
+ projectPoint(location) {
+ const latLng = new L.LatLng(location[0], location[1])
+ return {
+ x: this.map.latLngToLayerPoint(latLng).x + this.state.mapTransformX,
+ y: this.map.latLngToLayerPoint(latLng).y + this.state.mapTransformY
+ }
+ }
+
getClientDims() {
- const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect();
+ const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect()
return {
width: boundingClient.width,
@@ -118,8 +131,8 @@ class Map extends React.Component {
}
renderTiles() {
- const pane = this.map.getPanes().overlayPane;
- const { width, height } = this.getClientDims();
+ const pane = this.map.getPanes().overlayPane
+ const { width, height } = this.getClientDims()
return (
@@ -132,35 +145,42 @@ class Map extends React.Component {
>
- );
+ )
}
renderSites() {
return (
-
- );
+ )
+ }
+
+ renderShapes() {
+ return (
+
+ )
}
renderNarratives() {
return (
-
- );
+ )
}
/**
@@ -182,39 +202,35 @@ class Map extends React.Component {
renderEvents() {
return (
-
- );
+ )
}
renderSelected() {
return (
-
- );
+ )
}
renderMarkers() {
return (
-
+
)
}
@@ -222,19 +238,25 @@ class Map extends React.Component {
render() {
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 ? (
+
+ {this.renderTiles()}
+ {this.renderMarkers()}
+ {isShowingSites ? this.renderSites() : null}
+ {this.renderShapes()}
+ {this.renderEvents()}
+ {this.renderNarratives()}
+ {this.renderSelected()}
+
+ ) : null
return (
- {(this.map !== null) ? this.renderTiles() : ''}
- {(this.map !== null) ? this.renderMarkers() : ''}
- {(this.map !== null) && isShowingSites ? this.renderSites() : ''}
- {(this.map !== null) ? this.renderEvents() : ''}
- {(this.map !== null) ? this.renderNarratives() : ''}
- {(this.map !== null) ? this.renderSelected() : ''}
+ {innerMap}
- );
+ )
}
}
@@ -244,22 +266,24 @@ function mapStateToProps(state) {
locations: selectors.selectLocations(state),
narratives: selectors.selectNarratives(state),
categories: selectors.selectCategories(state),
- sites: selectors.getSites(state)
+ sites: selectors.getSites(state),
+ shapes: selectors.getShapes(state)
},
app: {
views: state.app.filters.views,
selected: state.app.selected,
highlighted: state.app.highlighted,
- mapAnchor: state.app.mapAnchor,
- mapBounds: state.app.filters.mapBounds,
+ map: state.app.map,
narrative: state.app.narrative,
flags: {
isShowingSites: state.app.flags.isShowingSites
}
},
ui: {
+ tiles: state.ui.tiles,
dom: state.ui.dom,
- narratives: state.ui.style.narratives
+ narratives: state.ui.style.narratives,
+ shapes: state.ui.style.shapes
}
}
}
diff --git a/src/components/MapSites.jsx b/src/components/MapSites.jsx
deleted file mode 100644
index 16d4a92..0000000
--- a/src/components/MapSites.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-
-class MapSites extends React.Component {
-
- projectPoint(location) {
- const latLng = new L.LatLng(location[0], location[1]);
- return {
- x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX,
- y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY
- };
- }
-
- renderSite(site) {
- const { x, y } = this.projectPoint([site.latitude, site.longitude]);
-
- return (
- {site.site}
-
- );
- }
-
- render () {
- if (!this.props.sites || !this.props.sites.length) return ;
-
- return (
-
- {this.props.sites.map(site => { return this.renderSite(site); })}
-
- )
- }
-
-}
-
-export default MapSites;
\ No newline at end of file
diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx
index 9191307..ea1f08d 100644
--- a/src/components/SourceOverlay.jsx
+++ b/src/components/SourceOverlay.jsx
@@ -145,7 +145,7 @@ class SourceOverlay extends React.Component {
this.onShiftGallery(-1)}>
- );
+ );
}
return (
@@ -175,7 +175,7 @@ class SourceOverlay extends React.Component {
-
{`${this.state.idx+1} / ${paths.length}`}
+ {/*
{`${this.state.idx+1} / ${paths.length}`}
*/}
{title?
{title}
: null}
{desc}
diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx
index 8d7104b..840f05f 100644
--- a/src/components/Timeline.jsx
+++ b/src/components/Timeline.jsx
@@ -1,98 +1,90 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import * as selectors from '../selectors';
-import hash from 'object-hash';
+import React from 'react'
+import { connect } from 'react-redux'
+import * as selectors from '../selectors'
+import hash from 'object-hash'
-import copy from '../js/data/copy.json';
-import { formatterWithYear, parseDate } from '../js/utilities';
-import TimelineHeader from './presentational/TimelineHeader';
-import TimelineAxis from './TimelineAxis.jsx';
-import TimelineClip from './presentational/TimelineClip';
-import TimelineHandles from './presentational/TimelineHandles.js';
-import TimelineZoomControls from './presentational/TimelineZoomControls.js';
-import TimelineLabels from './presentational/TimelineLabels.js';
-import TimelineMarkers from './presentational/TimelineMarkers.js'
-import TimelineEvents from './presentational/TimelineEvents.js';
-import TimelineCategories from './TimelineCategories.jsx';
+import copy from '../js/data/copy.json'
+import { formatterWithYear, parseDate } from '../js/utilities'
+import Header from './presentational/Timeline/Header'
+import Axis from './TimelineAxis.jsx'
+import Clip from './presentational/Timeline/Clip'
+import Handles from './presentational/Timeline/Handles.js'
+import ZoomControls from './presentational/Timeline/ZoomControls.js'
+import Labels from './presentational/Timeline/Labels.js'
+import Markers from './presentational/Timeline/Markers.js'
+import Events from './presentational/Timeline/Events.js'
+import Categories from './TimelineCategories.jsx'
class Timeline extends React.Component {
constructor(props) {
- super(props);
+ super(props)
this.styleDatetime = this.styleDatetime.bind(this)
this.getDatetimeX = this.getDatetimeX.bind(this)
this.onApplyZoom = this.onApplyZoom.bind(this)
this.svgRef = React.createRef()
this.state = {
isFolded: false,
- dims: {
- height: 140,
- width: 0,
- width_controls: 100,
- height_controls: 115,
- margin_left: 120,
- margin_top: 20,
- trackHeight: 80
- },
+ dims: props.app.timeline.dimensions,
scaleX: null,
scaleY: null,
timerange: [null, null],
dragPos0: null,
transitionDuration: 300
- };
+ }
}
componentDidMount() {
- this.computeDims();
- this.addEventListeners();
+ this.computeDims()
+ this.addEventListeners()
}
componentWillReceiveProps(nextProps) {
if (hash(nextProps) !== hash(this.props)) {
this.setState({
- timerange: nextProps.app.timerange,
+ timerange: nextProps.app.timeline.range,
scaleX: this.makeScaleX()
- });
+ })
}
if (hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) {
this.setState({
scaleY: this.makeScaleY(nextProps.domain.categories)
- });
+ })
}
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
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() {
- window.addEventListener('resize', () => { this.computeDims(); });
- let element = document.querySelector('.timeline-wrapper');
+ window.addEventListener('resize', () => { this.computeDims() })
+ let element = document.querySelector('.timeline-wrapper')
element.addEventListener("transitionend", (event) => {
- this.computeDims();
- });
+ this.computeDims()
+ })
}
makeScaleX() {
return d3.scaleTime()
.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) {
- const tickHeight = 15;
- const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2);
+ const tickHeight = 15
+ const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2)
return d3.scaleOrdinal()
.domain(categories)
- .range(catsYpos);
+ .range(catsYpos)
}
componentDidUpdate(prevProps, prevState) {
if (prevState.timerange !== this.state.timerange) {
- this.setState({ scaleX: this.makeScaleX() });
+ this.setState({ scaleX: this.makeScaleX() })
}
}
@@ -101,25 +93,31 @@ class Timeline extends React.Component {
*/
getTimeScaleExtent() {
if (!this.state.scaleX) return 0
- const timeDomain = this.state.scaleX.domain();
- return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000;
+ const timeDomain = this.state.scaleX.domain()
+ return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000
}
onClickArrow() {
this.setState((prevState, props) => {
- return {isFolded: !prevState.isFolded};
- });
+ return {isFolded: !prevState.isFolded}
+ })
}
computeDims() {
- const dom = this.props.ui.dom.timeline;
+ const dom = this.props.ui.dom.timeline
if (document.querySelector(`#${dom}`) !== null) {
- const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect();
+ const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect()
this.setState({
- dims: Object.assign({}, this.state.dims, { width: boundingClient.width })
- }, () => { this.setState({ scaleX: this.makeScaleX() })
- });
+ dims: {
+ ...this.state.dims,
+ width: boundingClient.width
+ }
+ },
+ () => {
+ this.setState({ scaleX: this.makeScaleX()
+ })
+ })
}
}
@@ -128,34 +126,34 @@ class Timeline extends React.Component {
* @param {String} direction: 'forward' / 'backwards'
*/
onMoveTime(direction) {
- this.props.methods.onSelect();
- const extent = this.getTimeScaleExtent();
- const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2);
+ this.props.methods.onSelect()
+ const extent = this.getTimeScaleExtent()
+ const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
// if forward
- let domain0 = newCentralTime;
- let domainF = d3.timeMinute.offset(newCentralTime, extent);
+ let domain0 = newCentralTime
+ let domainF = d3.timeMinute.offset(newCentralTime, extent)
// if backwards
if (direction === 'backwards') {
- domain0 = d3.timeMinute.offset(newCentralTime, -extent);
- domainF = newCentralTime;
+ domain0 = d3.timeMinute.offset(newCentralTime, -extent)
+ domainF = newCentralTime
}
this.setState({ timerange: [domain0, domainF] }, () => {
- this.props.methods.onUpdateTimerange(this.state.timerange);
- });
+ this.props.methods.onUpdateTimerange(this.state.timerange)
+ })
}
onCenterTime(newCentralTime) {
- const extent = this.getTimeScaleExtent();
+ const extent = this.getTimeScaleExtent()
- const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2);
- const domainF = d3.timeMinute.offset(newCentralTime, +extent/2);
+ const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2)
+ const domainF = d3.timeMinute.offset(newCentralTime, +extent/2)
this.setState({ timerange: [domain0, domainF] }, () => {
- this.props.methods.onUpdateTimerange(this.state.timerange);
- });
+ this.props.methods.onUpdateTimerange(this.state.timerange)
+ })
}
/**
@@ -164,7 +162,7 @@ class Timeline extends React.Component {
* Used for updates in the middle of a transition, for performance purposes
*/
onSoftTimeRangeUpdate(timerange) {
- this.setState({ timerange });
+ this.setState({ timerange })
}
/**
@@ -172,54 +170,55 @@ class Timeline extends React.Component {
* @param {object} zoom: zoom level from zoomLevels
*/
onApplyZoom(zoom) {
- const extent = this.getTimeScaleExtent();
- const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2);
+ const extent = this.getTimeScaleExtent()
+ const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
this.setState({ timerange: [
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) {
- this.setState({ transitionDuration: (isTransition) ? 300 : 0 });
+ this.setState({ transitionDuration: (isTransition) ? 300 : 0 })
}
/*
* Setup drag behavior
*/
onDragStart() {
- d3.event.sourceEvent.stopPropagation();
+ d3.event.sourceEvent.stopPropagation()
this.setState({
dragPos0: d3.event.x
}, () => {
- this.toggleTransition(false);
- });
+ this.toggleTransition(false)
+ })
}
/*
* Drag and update
*/
onDrag() {
- const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime();
- const dragNow = this.state.scaleX.invert(d3.event.x).getTime();
- const timeShift = (drag0 - dragNow) / 1000;
+ const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime()
+ const dragNow = this.state.scaleX.invert(d3.event.x).getTime()
+ const timeShift = (drag0 - dragNow) / 1000
- const newDomain0 = d3.timeSecond.offset(this.props.app.timerange[0], timeShift);
- const newDomainF = d3.timeSecond.offset(this.props.app.timerange[1], timeShift);
+ const { range } = this.props.app.timeline
+ const newDomain0 = d3.timeSecond.offset(range[0], timeShift)
+ const newDomainF = d3.timeSecond.offset(range[1], timeShift)
// Updates components without updating timerange
- this.onSoftTimeRangeUpdate([newDomain0, newDomainF]);
+ this.onSoftTimeRangeUpdate([newDomain0, newDomainF])
}
/**
* Stop dragging and update data
*/
onDragEnd() {
- this.toggleTransition(true);
- this.props.methods.onUpdateTimerange(this.state.timerange);
+ this.toggleTransition(true)
+ this.props.methods.onUpdateTimerange(this.state.timerange)
}
getDatetimeX(dt) {
@@ -245,17 +244,17 @@ class Timeline extends React.Component {
render() {
const { isNarrative, app, ui } = this.props
- let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`;
- classes += (app.narrative !== null) ? ' narrative-mode' : '';
- const dims = this.state.dims;
+ let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`
+ classes += (app.narrative !== null) ? ' narrative-mode' : ''
+ const { dims } = this.state
return (
-
{ this.onClickArrow(); }}
+ onClick={() => { this.onClickArrow() }}
hideInfo={isNarrative}
/>
@@ -265,43 +264,44 @@ class Timeline extends React.Component {
width={dims.width}
height={dims.height}
>
-
-
- { this.onDragStart() }}
onDrag={() => { this.onDrag() }}
onDragEnd={() => { this.onDragEnd() }}
categories={this.props.domain.categories}
/>
- { this.onMoveTime(dir) }}
/>
-
-
- */}
+
-
- );
+ )
}
}
@@ -328,10 +328,9 @@ function mapStateToProps(state) {
narratives: state.domain.narratives
},
app: {
- timerange: selectors.getTimeRange(state),
selected: state.app.selected,
language: state.app.language,
- zoomLevels: state.app.zoomLevels,
+ timeline: state.app.timeline,
narrative: state.app.narrative
},
ui: {
@@ -340,4 +339,4 @@ function mapStateToProps(state) {
}
}
-export default connect(mapStateToProps)(Timeline);
+export default connect(mapStateToProps)(Timeline)
diff --git a/src/components/TimelineCategories.jsx b/src/components/TimelineCategories.jsx
index 25dce29..6ecdad4 100644
--- a/src/components/TimelineCategories.jsx
+++ b/src/components/TimelineCategories.jsx
@@ -24,14 +24,10 @@ class TimelineCategories extends React.Component {
}
}
- getY(idx) {
- return (idx + 1) * this.props.dims.trackHeight / this.props.categories.length + 7.5;
- }
-
renderCategory(category, idx) {
const dims = this.props.dims;
return (
-
+
{category.category}
diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx
index 1596f29..9129ea6 100644
--- a/src/components/Toolbar.jsx
+++ b/src/components/Toolbar.jsx
@@ -1,15 +1,15 @@
-import React from 'react';
+import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as actions from '../actions'
import * as selectors from '../selectors'
-import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
-import Search from './Search.jsx';
-import TagListPanel from './TagListPanel.jsx';
-import ToolbarBottomActions from './ToolbarBottomActions.jsx';
-import copy from '../js/data/copy.json';
-import { trimAndEllipse } from '../js/utilities.js';
+import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
+import Search from './Search.jsx'
+import TagListPanel from './TagListPanel.jsx'
+import ToolbarBottomActions from './ToolbarBottomActions.jsx'
+import copy from '../js/data/copy.json'
+import { trimAndEllipse } from '../js/utilities.js'
class Toolbar extends React.Component {
constructor(props) {
@@ -19,7 +19,7 @@ class Toolbar extends React.Component {
selectTab(selected) {
const _selected = (this.state._selected === selected) ? -1 : selected
- this.setState({ _selected });
+ this.setState({ _selected })
}
renderClosePanel() {
@@ -27,7 +27,7 @@ class Toolbar extends React.Component {
- );
+ )
}
renderSearch() {
@@ -49,7 +49,7 @@ class Toolbar extends React.Component {
goToNarrative(narrative) {
this.selectTab(-1) // set all unselected within this component
- this.props.methods.onSelectNarrative(narrative);
+ this.props.methods.onSelectNarrative(narrative)
}
renderToolbarNarrativePanel() {
@@ -68,7 +68,7 @@ class Toolbar extends React.Component {
)
})}
- );
+ )
}
renderToolbarTagPanel() {
@@ -88,23 +88,23 @@ class Toolbar extends React.Component {
)
}
- return '';
+ return ''
}
renderToolbarTab(_selected, label, icon_key) {
- const isActive = (this.state._selected === _selected);
- let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab';
+ const isActive = (this.state._selected === _selected)
+ let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'
return (
- { this.selectTab(_selected); }}>
+
{ this.selectTab(_selected) }}>
{icon_key}
{label}
- );
+ )
}
renderToolbarPanels() {
- let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded';
+ let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded'
return (
{this.renderClosePanel()}
@@ -119,25 +119,26 @@ class Toolbar extends React.Component {
renderToolbarNavs() {
if (this.props.narratives) {
return this.props.narratives.map((nar, idx) => {
- const isActive = (idx === this.state._selected);
+ const isActive = (idx === this.state._selected)
- let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab';
+ let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'
return (
-
{ this.selectTab(idx); }}>
+
{ this.selectTab(idx) }}>
{nar.label}
- );
+ )
})
}
- return '';
+ return ''
}
renderToolbarTabs() {
- const title = copy[this.props.language].toolbar.title;
- const narratives_label = copy[this.props.language].toolbar.narratives_label;
- const tags_label = copy[this.props.language].toolbar.tags_label;
- const isTags = this.props.tags && this.props.tags.children;
+ let title = copy[this.props.language].toolbar.title
+ if (process.env.title) title = process.env.title
+ const narratives_label = copy[this.props.language].toolbar.narratives_label
+ const tags_label = copy[this.props.language].toolbar.tags_label
+ const isTags = this.props.tags && this.props.tags.children
return (
@@ -165,7 +166,7 @@ class Toolbar extends React.Component {
{this.renderToolbarTabs()}
{this.renderToolbarPanels()}
- );
+ )
}
}
diff --git a/src/components/ToolbarBottomActions.jsx b/src/components/ToolbarBottomActions.jsx
index e20a4be..b35990d 100644
--- a/src/components/ToolbarBottomActions.jsx
+++ b/src/components/ToolbarBottomActions.jsx
@@ -13,10 +13,10 @@ function ToolbarBottomActions (props) {
{/* onClick={(view) => this.toggleMapViews(view)} */}
{/* isEnabled={this.props.viewFilters.routes} */}
{/* /> */}
-
+ {process.env.features.USE_SITES ?
: null}
{/*
this.toggleMapViews(view)} */}
{/* isEnabled={this.props.viewFilters.coevents} */}
diff --git a/src/components/presentational/.DS_Store b/src/components/presentational/.DS_Store
new file mode 100644
index 0000000..1e61c25
Binary files /dev/null and b/src/components/presentational/.DS_Store differ
diff --git a/src/components/presentational/CardCaret.js b/src/components/presentational/Card/Caret.js
similarity index 100%
rename from src/components/presentational/CardCaret.js
rename to src/components/presentational/Card/Caret.js
diff --git a/src/components/presentational/CardCategory.js b/src/components/presentational/Card/Category.js
similarity index 84%
rename from src/components/presentational/CardCategory.js
rename to src/components/presentational/Card/Category.js
index 20b396d..ab6eb4c 100644
--- a/src/components/presentational/CardCategory.js
+++ b/src/components/presentational/Card/Category.js
@@ -1,6 +1,6 @@
import React from 'react';
-import { capitalizeFirstLetter } from '../../js/utilities.js';
+import { capitalizeFirstLetter } from '../../../js/utilities.js';
const CardCategory = ({ categoryTitle, categoryLabel, color }) => (
diff --git a/src/components/presentational/CardLocation.js b/src/components/presentational/Card/Location.js
similarity index 84%
rename from src/components/presentational/CardLocation.js
rename to src/components/presentational/Card/Location.js
index e9598fa..5025c5b 100644
--- a/src/components/presentational/CardLocation.js
+++ b/src/components/presentational/Card/Location.js
@@ -1,7 +1,7 @@
import React from 'react';
-import copy from '../../js/data/copy.json';
-import { isNotNullNorUndefined } from '../../js/utilities';
+import copy from '../../../js/data/copy.json';
+import { isNotNullNorUndefined } from '../../../js/utilities';
const CardLocation = ({ language, location }) => {
diff --git a/src/components/presentational/CardNarrative.js b/src/components/presentational/Card/Narrative.js
similarity index 86%
rename from src/components/presentational/CardNarrative.js
rename to src/components/presentational/Card/Narrative.js
index 49f590d..adc88f2 100644
--- a/src/components/presentational/CardNarrative.js
+++ b/src/components/presentational/Card/Narrative.js
@@ -1,6 +1,6 @@
import React from 'react';
-import CardNarrativeLink from './CardNarrativeLink';
+import CardNarrativeLink from './NarrativeLink';
const CardNarrative = (props) => (
diff --git a/src/components/presentational/CardNarrativeLink.js b/src/components/presentational/Card/NarrativeLink.js
similarity index 100%
rename from src/components/presentational/CardNarrativeLink.js
rename to src/components/presentational/Card/NarrativeLink.js
diff --git a/src/components/presentational/CardSource.js b/src/components/presentational/Card/Source.js
similarity index 96%
rename from src/components/presentational/CardSource.js
rename to src/components/presentational/Card/Source.js
index 9b3da5b..7447ca6 100644
--- a/src/components/presentational/CardSource.js
+++ b/src/components/presentational/Card/Source.js
@@ -1,9 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Spinner from './Spinner'
import Img from 'react-image'
-import copy from '../../js/data/copy.json'
+import Spinner from '../Spinner'
+import copy from '../../../js/data/copy.json'
const CardSource = ({ source, isLoading, onClickHandler }) => {
function renderIconText(type) {
diff --git a/src/components/presentational/CardSummary.js b/src/components/presentational/Card/Summary.js
similarity index 88%
rename from src/components/presentational/CardSummary.js
rename to src/components/presentational/Card/Summary.js
index 3e0cdbf..ac5bbd2 100644
--- a/src/components/presentational/CardSummary.js
+++ b/src/components/presentational/Card/Summary.js
@@ -1,6 +1,6 @@
import React from 'react';
-import copy from '../../js/data/copy.json';
+import copy from '../../../js/data/copy.json';
const CardSummary = ({ language, description, isHighlighted }) => {
diff --git a/src/components/presentational/CardTags.js b/src/components/presentational/Card/Tags.js
similarity index 94%
rename from src/components/presentational/CardTags.js
rename to src/components/presentational/Card/Tags.js
index 6841cd4..2bc4cc4 100644
--- a/src/components/presentational/CardTags.js
+++ b/src/components/presentational/Card/Tags.js
@@ -1,6 +1,6 @@
import React from 'react';
-import copy from '../../js/data/copy.json';
+import copy from '../../../js/data/copy.json';
const CardTags = ({ tags, language }) => {
const tags_lang = copy[language].cardstack.tags;
diff --git a/src/components/presentational/CardTimestamp.js b/src/components/presentational/Card/Timestamp.js
similarity index 87%
rename from src/components/presentational/CardTimestamp.js
rename to src/components/presentational/Card/Timestamp.js
index 0317896..6ac69e6 100644
--- a/src/components/presentational/CardTimestamp.js
+++ b/src/components/presentational/Card/Timestamp.js
@@ -1,7 +1,7 @@
import React from 'react';
-import copy from '../../js/data/copy.json';
-import { isNotNullNorUndefined } from '../../js/utilities';
+import copy from '../../../js/data/copy.json';
+import { isNotNullNorUndefined } from '../../../js/utilities';
const CardTimestamp = ({ makeTimelabel, language, timestamp }) => {
diff --git a/src/components/MapDefsMarkers.jsx b/src/components/presentational/Map/DefsMarkers.jsx
similarity index 100%
rename from src/components/MapDefsMarkers.jsx
rename to src/components/presentational/Map/DefsMarkers.jsx
diff --git a/src/components/MapEvents.jsx b/src/components/presentational/Map/Events.jsx
similarity index 55%
rename from src/components/MapEvents.jsx
rename to src/components/presentational/Map/Events.jsx
index 68c4fe4..39a7901 100644
--- a/src/components/MapEvents.jsx
+++ b/src/components/presentational/Map/Events.jsx
@@ -1,19 +1,10 @@
import React from 'react';
import { Portal } from 'react-portal';
-class MapEvents extends React.Component {
-
- projectPoint(location) {
- const latLng = new L.LatLng(location[0], location[1]);
- return {
- x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX,
- y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY
- };
- }
-
- getLocationEventsDistribution(location) {
+function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, narrative, onSelect, svg, locations }){
+ function getLocationEventsDistribution(location) {
const eventCount = {};
- const categories = this.props.categories;
+ const categories = categories;
categories.forEach(cat => {
eventCount[cat.category] = [];
@@ -26,7 +17,7 @@ class MapEvents extends React.Component {
return eventCount;
}
- renderLocation(location) {
+ function renderLocation(location) {
/**
{
events: [...],
@@ -35,23 +26,23 @@ class MapEvents extends React.Component {
longitude: '32.2'
}
*/
- const { x, y } = this.projectPoint([location.latitude, location.longitude]);
- // const eventsByCategory = this.getLocationEventsDistribution(location);
+ const { x, y } = projectPoint([location.latitude, location.longitude]);
+ // const eventsByCategory = getLocationEventsDistribution(location);
const locCategory = location.events.length > 0 ? location.events[0].category : 'default'
- const customStyles = this.props.styleLocation ? this.props.styleLocation(location) : null
+ const customStyles = styleLocation ? styleLocation(location) : null
const extraStyles = customStyles[0]
const extraRender = customStyles[1]
const styles = ({
- fill: this.props.getCategoryColor(locCategory),
+ fill: getCategoryColor(locCategory),
fillOpacity: 1,
...customStyles[0]
})
// in narrative mode, only render events in narrative
- if (this.props.narrative) {
- const { steps } = this.props.narrative
+ if (narrative) {
+ const { steps } = narrative
const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id)
const eventsInNarrative = location.events.filter(onlyIfInNarrative)
@@ -64,7 +55,7 @@ class MapEvents extends React.Component {
this.props.onSelect(location.events)}
+ onClick={() => onSelect(location.events)}
>
- {this.props.locations.map(loc => this.renderLocation(loc))}
-
- );
- }
+ return (
+
+ {locations.map(renderLocation)}
+
+ );
}
export default MapEvents;
diff --git a/src/components/MapNarratives.jsx b/src/components/presentational/Map/Narratives.jsx
similarity index 50%
rename from src/components/MapNarratives.jsx
rename to src/components/presentational/Map/Narratives.jsx
index 9d7248b..84452e8 100644
--- a/src/components/MapNarratives.jsx
+++ b/src/components/presentational/Map/Narratives.jsx
@@ -1,76 +1,67 @@
import React from 'react'
import { Portal } from 'react-portal'
-class MapNarratives extends React.Component {
-
- projectPoint(location) {
- const latLng = new L.LatLng(location[0], location[1])
- return {
- x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX,
- y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY
- }
- }
-
- getNarrativeStyle(narrativeId) {
- const styleName = (narrativeId && narrativeId in this.props.narrativeProps)
+function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) {
+ function getNarrativeStyle(narrativeId) {
+ const styleName = (narrativeId && narrativeId in styles)
? narrativeId
: 'default'
- return this.props.narrativeProps[styleName]
+ return styles[styleName]
}
- getStepStyle(name) {
+ function getStepStyle(name) {
if (name === 'None') return null
- return this.props.narrativeProps.stepStyles[name]
+ return styles.stepStyles[name]
}
- hasNoLocation(step) {
+ function hasNoLocation(step) {
return (step.latitude === '' || step.longitude === '')
}
- renderNarrativeStep(idx, n) {
+ function renderNarrativeStep(idx, n) {
const step = n.steps[idx]
const step2 = n.steps[idx + 1]
// don't draw if one of the steps has no location
- if (this.hasNoLocation(step) || this.hasNoLocation(step2))
+ if (hasNoLocation(step) || hasNoLocation(step2))
return null
// 0 if not in narrative mode, 1 if active narrative, 0.1 if inactive
let styles = {
strokeOpacity: (n === null) ? 0
- : (step && (n.id === this.props.narrative.id)) ? 1 : 0.1,
+ : (step && (n.id === narrative.id)) ? 1 : 0.1,
strokeWidth: 0,
strokeDasharray: 'none',
stroke: 'none'
}
- const p1 = this.projectPoint([step.latitude, step.longitude])
- const p2 = this.projectPoint([step2.latitude, step2.longitude])
+ const p1 = projectPoint([step.latitude, step.longitude])
+ const p2 = projectPoint([step2.latitude, step2.longitude])
if (step) {
if (process.env.features.NARRATIVE_STEP_STYLES) {
const _idx = step.narratives.indexOf(n.id)
const stepStyle = step.narrative___stepStyles[_idx]
- return this._renderNarrativeStep(
+ return _renderNarrativeStep(
p1,
p2,
- { ...styles, ...this.getStepStyle(stepStyle) }
+ { ...styles, ...getStepStyle(stepStyle) }
)
// otherwise steps are styled per narrative
} else {
styles = {
...styles,
- ...this.getNarrativeStyle(n.id)
+ ...getNarrativeStyle(n.id)
}
- return this._renderNarrativeStep(p1,p2,styles)
+ return _renderNarrativeStep(p1,p2,styles)
}
}
}
- _renderNarrativeStep(p1, p2, styles) {
+ function _renderNarrativeStep(p1, p2, styles) {
const { stroke, strokeWidth, strokeDasharray, strokeOpacity } = styles
return (
this.props.onSelectNarrative(n)}
+ onClick={() => onSelectNarrative(n)}
style={{
strokeWidth,
strokeDasharray,
@@ -93,25 +84,23 @@ class MapNarratives extends React.Component {
}
- renderNarrative(n) {
+ function renderNarrative(n) {
const steps = n.steps.slice(0, n.steps.length - 1)
return (
- {steps.map((s, idx) => this.renderNarrativeStep(idx, n))}
+ {steps.map((s, idx) => renderNarrativeStep(idx, n))}
)
}
- render() {
- if (this.props.narrative === null) return ()
+ if (narrative === null) return ()
- return (
-
- {this.props.narratives.map(n => this.renderNarrative(n))}
-
- )
- }
+ return (
+
+ {narratives.map(n => renderNarrative(n))}
+
+ )
}
export default MapNarratives
diff --git a/src/components/MapSelectedEvents.jsx b/src/components/presentational/Map/SelectedEvents.jsx
similarity index 66%
rename from src/components/MapSelectedEvents.jsx
rename to src/components/presentational/Map/SelectedEvents.jsx
index 8c48bc8..88c3036 100644
--- a/src/components/MapSelectedEvents.jsx
+++ b/src/components/presentational/Map/SelectedEvents.jsx
@@ -2,17 +2,8 @@ import React from 'react';
import { Portal } from 'react-portal';
class MapSelectedEvents extends React.Component {
-
- projectPoint(location) {
- const latLng = new L.LatLng(location[0], location[1]);
- return {
- x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX,
- y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY
- };
- }
-
renderMarker (event) {
- const { x, y } = this.projectPoint([event.latitude, event.longitude]);
+ const { x, y } = this.props.projectPoint([event.latitude, event.longitude]);
return (
{
+ if (idx < shape.points.length - 1) {
+ const p2 = points[idx+1]
+ lineCoords.push({
+ x1: p1.x,
+ y1: p1.y,
+ x2: p2.x,
+ y2: p2.y
+ })
+ }
+ })
+
+ return lineCoords.map(coords => {
+ const shapeStyles = (shape.name in styles)
+ ? styles[shape.name]
+ : styles.default
+
+ return (
+
+
+ )
+ })
+ }
+
+ if (!shapes || !shapes.length) return null
+
+ return (
+
+
+ {shapes.map(renderShape)}
+
+
+ )
+
+}
+
+export default MapShapes
diff --git a/src/components/presentational/Map/Sites.jsx b/src/components/presentational/Map/Sites.jsx
new file mode 100644
index 0000000..9d49885
--- /dev/null
+++ b/src/components/presentational/Map/Sites.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+function MapSites({ sites, projectPoint }) {
+ function renderSite(site) {
+ const { x, y } = projectPoint([site.latitude, site.longitude]);
+
+ return (
+ {site.site}
+
+ );
+ }
+
+ if (!sites || !sites.length) return null;
+
+ return (
+
+ {sites.map(renderSite)}
+
+ )
+
+}
+
+export default MapSites;
diff --git a/src/components/presentational/NarrativeAdjust.js b/src/components/presentational/Narrative/Adjust.js
similarity index 100%
rename from src/components/presentational/NarrativeAdjust.js
rename to src/components/presentational/Narrative/Adjust.js
diff --git a/src/components/presentational/NarrativeCard.js b/src/components/presentational/Narrative/Card.js
similarity index 94%
rename from src/components/presentational/NarrativeCard.js
rename to src/components/presentational/Narrative/Card.js
index b47e10c..f71a28f 100644
--- a/src/components/presentational/NarrativeCard.js
+++ b/src/components/presentational/Narrative/Card.js
@@ -1,6 +1,6 @@
import React from 'react'
import { connect } from 'react-redux'
-import { selectActiveNarrative } from '../../selectors'
+import { selectActiveNarrative } from '../../../selectors'
function NarrativeCard ({ narrative }) {
// no display if no narrative
diff --git a/src/components/presentational/NarrativeClose.js b/src/components/presentational/Narrative/Close.js
similarity index 100%
rename from src/components/presentational/NarrativeClose.js
rename to src/components/presentational/Narrative/Close.js
diff --git a/src/components/presentational/NarrativeControls.js b/src/components/presentational/Narrative/Controls.js
similarity index 71%
rename from src/components/presentational/NarrativeControls.js
rename to src/components/presentational/Narrative/Controls.js
index 5a120dd..f80bf63 100644
--- a/src/components/presentational/NarrativeControls.js
+++ b/src/components/presentational/Narrative/Controls.js
@@ -1,7 +1,7 @@
import React from 'react'
-import NarrativeCard from './NarrativeCard'
-import NarrativeAdjust from './NarrativeAdjust'
-import NarrativeClose from './NarrativeClose'
+import Card from './Card'
+import Adjust from './Adjust'
+import Close from './Close'
export default ({ narrative, methods }) => {
if (!narrative) return null
@@ -12,18 +12,18 @@ export default ({ narrative, methods }) => {
return (
-
-
+
-
- methods.onSelectNarrative(null)}
closeMsg='-- exit from narrative --'
/>
diff --git a/src/components/presentational/TimelineClip.js b/src/components/presentational/Timeline/Clip.js
similarity index 87%
rename from src/components/presentational/TimelineClip.js
rename to src/components/presentational/Timeline/Clip.js
index 29491da..11f4c79 100644
--- a/src/components/presentational/TimelineClip.js
+++ b/src/components/presentational/Timeline/Clip.js
@@ -3,8 +3,8 @@ import React from 'react';
const TimelineClip = ({ dims }) => (
diff --git a/src/components/presentational/DatetimeDot.js b/src/components/presentational/Timeline/DatetimeDot.js
similarity index 100%
rename from src/components/presentational/DatetimeDot.js
rename to src/components/presentational/Timeline/DatetimeDot.js
diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/Timeline/Events.js
similarity index 100%
rename from src/components/presentational/TimelineEvents.js
rename to src/components/presentational/Timeline/Events.js
diff --git a/src/components/presentational/TimelineHandles.js b/src/components/presentational/Timeline/Handles.js
similarity index 86%
rename from src/components/presentational/TimelineHandles.js
rename to src/components/presentational/Timeline/Handles.js
index 3e65f9b..12e9da9 100644
--- a/src/components/presentational/TimelineHandles.js
+++ b/src/components/presentational/Timeline/Handles.js
@@ -5,14 +5,14 @@ const TimelineHandles = ({ dims, onMoveTime }) => {
return (
onMoveTime('backwards')}
>
onMoveTime('forward')}
>
@@ -23,4 +23,4 @@ const TimelineHandles = ({ dims, onMoveTime }) => {
}
-export default TimelineHandles;
\ No newline at end of file
+export default TimelineHandles;
diff --git a/src/components/presentational/TimelineHeader.js b/src/components/presentational/Timeline/Header.js
similarity index 100%
rename from src/components/presentational/TimelineHeader.js
rename to src/components/presentational/Timeline/Header.js
diff --git a/src/components/presentational/Timeline/Labels.js b/src/components/presentational/Timeline/Labels.js
new file mode 100644
index 0000000..60e260e
--- /dev/null
+++ b/src/components/presentational/Timeline/Labels.js
@@ -0,0 +1,44 @@
+import React from 'react';
+
+import { formatterWithYear } from '../../../js/utilities.js';
+
+const TimelineLabels = ({ dims, timelabels }) => {
+
+ return (
+
+
+
+
+
+
+ {formatterWithYear(timelabels[0])}
+
+
+ {formatterWithYear(timelabels[1])}
+
+
+ )
+}
+
+export default TimelineLabels;
diff --git a/src/components/presentational/TimelineMarkers.js b/src/components/presentational/Timeline/Markers.js
similarity index 100%
rename from src/components/presentational/TimelineMarkers.js
rename to src/components/presentational/Timeline/Markers.js
diff --git a/src/components/presentational/TimelineZoomControls.js b/src/components/presentational/Timeline/ZoomControls.js
similarity index 100%
rename from src/components/presentational/TimelineZoomControls.js
rename to src/components/presentational/Timeline/ZoomControls.js
diff --git a/src/components/presentational/TimelineLabels.js b/src/components/presentational/TimelineLabels.js
deleted file mode 100644
index 36466ba..0000000
--- a/src/components/presentational/TimelineLabels.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-
-import { formatterWithYear } from '../../js/utilities.js';
-
-const TimelineLabels = ({ dims, timelabels }) => {
-
- return (
-
-
-
-
-
- {/* */}
- {/* {formatterWithYear(timelabels[0])} */}
- {/* */}
- {/* */}
- {/* {formatterWithYear(timelabels[1])} */}
- {/* */}
-
- )
-}
-
-export default TimelineLabels;
diff --git a/src/reducers/app.js b/src/reducers/app.js
index 43e39aa..bc79b05 100644
--- a/src/reducers/app.js
+++ b/src/reducers/app.js
@@ -36,8 +36,8 @@ function updateSelected(appState, action) {
}
function updateNarrative(appState, action) {
- let minTime = appState.filters.timerange[0]
- let maxTime = appState.filters.timerange[1]
+ let minTime = appState.timeline.range[0]
+ let maxTime = appState.timeline.range[1]
let cornerBound0 = [180, 180]
let cornerBound1 = [-180, -180]
@@ -156,11 +156,13 @@ function updateCategoryFilters(appState, action) {
}
function updateTimeRange(appState, action) { // XXX
- return Object.assign({}, appState, {
- filters: Object.assign({}, appState.filters, {
- timerange: action.timerange
- }),
- })
+ return {
+ ...appState,
+ timeline: {
+ ...appState.timeline,
+ range: action.timerange
+ },
+ }
}
function resetAllFilters(appState) { // XXX
diff --git a/src/reducers/schema/shapeSchema.js b/src/reducers/schema/shapeSchema.js
new file mode 100644
index 0000000..d02079f
--- /dev/null
+++ b/src/reducers/schema/shapeSchema.js
@@ -0,0 +1,8 @@
+import Joi from 'joi'
+
+const shapeSchema = Joi.object().keys({
+ name: Joi.string().required(),
+ items: Joi.array().required()
+})
+
+export default shapeSchema
diff --git a/src/reducers/utils/validators.js b/src/reducers/utils/validators.js
index 42bf3d3..cbd13fd 100644
--- a/src/reducers/utils/validators.js
+++ b/src/reducers/utils/validators.js
@@ -1,12 +1,13 @@
-import Joi from 'joi';
+import Joi from 'joi'
-import eventSchema from '../schema/eventSchema';
-import categorySchema from '../schema/categorySchema';
-import siteSchema from '../schema/siteSchema';
-import narrativeSchema from '../schema/narrativeSchema';
+import eventSchema from '../schema/eventSchema'
+import categorySchema from '../schema/categorySchema'
+import siteSchema from '../schema/siteSchema'
+import narrativeSchema from '../schema/narrativeSchema'
import sourceSchema from '../schema/sourceSchema'
+import shapeSchema from '../schema/shapeSchema'
-import { capitalize } from './helpers.js';
+import { capitalize } from './helpers.js'
/*
* Create an error notification object
@@ -21,8 +22,8 @@ function makeError(type, id, message) {
}
-const isLeaf = node => (Object.keys(node.children).length === 0);
-const isDuplicate = (node, set) => { return (set.has(node.key)); };
+const isLeaf = node => (Object.keys(node.children).length === 0)
+const isDuplicate = (node, set) => { return (set.has(node.key)) }
/*
@@ -61,8 +62,9 @@ export function validateDomain (domain) {
sites: [],
narratives: [],
sources: {},
+ tags: {},
+ shapes: [],
notifications: domain.notifications,
- tags: {}
}
const discardedDomain = {
@@ -71,18 +73,19 @@ export function validateDomain (domain) {
sites: [],
narratives: [],
sources: [],
+ shapes: []
}
function validateArrayItem(item, domainKey, schema) {
- const result = Joi.validate(item, schema);
+ const result = Joi.validate(item, schema)
if (result.error !== null) {
- const id = item.id || '-';
- const domainStr = capitalize(domainKey);
- const error = makeError(domainStr, id, result.error.message);
+ const id = item.id || '-'
+ const domainStr = capitalize(domainKey)
+ const error = makeError(domainStr, id, result.error.message)
- discardedDomain[domainKey].push(Object.assign(item, { error }));
+ discardedDomain[domainKey].push(Object.assign(item, { error }))
} else {
- sanitizedDomain[domainKey].push(item);
+ sanitizedDomain[domainKey].push(item)
}
}
@@ -109,29 +112,38 @@ export function validateDomain (domain) {
})
}
- validateArray(domain.events, 'events', eventSchema);
- validateArray(domain.categories, 'categories', categorySchema);
- validateArray(domain.sites, 'sites', siteSchema);
- validateArray(domain.narratives, 'narratives', narrativeSchema);
- validateObject(domain.sources, 'sources', sourceSchema);
+ validateArray(domain.events, 'events', eventSchema)
+ validateArray(domain.categories, 'categories', categorySchema)
+ validateArray(domain.sites, 'sites', siteSchema)
+ validateArray(domain.narratives, 'narratives', narrativeSchema)
+ validateObject(domain.sources, 'sources', sourceSchema)
+ validateObject(domain.shapes, 'shapes', shapeSchema)
+ // NB: [lat, lon] array is best format for projecting into map
+ sanitizedDomain.shapes = sanitizedDomain.shapes.map(shape => ({
+ name: shape.name,
+ points: shape.items.map(coords => (
+ coords.replace(/\s/g, '').split(',')
+ ))
+ })
+ )
// Message the number of failed items in domain
Object.keys(discardedDomain).forEach(disc => {
- const len = discardedDomain[disc].length;
+ const len = discardedDomain[disc].length
if (len) {
sanitizedDomain.notifications.push({
message: `${len} invalid ${disc} not displayed.`,
items: discardedDomain[disc],
type: 'error'
- });
+ })
}
- });
+ })
// Validate uniqueness of tags
- const tagSet = new Set([]);
- const duplicateTags = [];
- validateTree(domain.tags, {}, tagSet, duplicateTags);
+ const tagSet = new Set([])
+ const duplicateTags = []
+ validateTree(domain.tags, {}, tagSet, duplicateTags)
// Duplicated tags
if (duplicateTags.length > 0) {
@@ -139,9 +151,9 @@ export function validateDomain (domain) {
message: `Tags are required to be unique. Ignoring duplicates for now.`,
items: duplicateTags,
type: 'error'
- });
+ })
}
- sanitizedDomain.tags = domain.tags;
+ sanitizedDomain.tags = domain.tags
- return sanitizedDomain;
+ return sanitizedDomain
}
diff --git a/src/scss/map.scss b/src/scss/map.scss
index a1d90b2..054895e 100644
--- a/src/scss/map.scss
+++ b/src/scss/map.scss
@@ -16,11 +16,11 @@
.leaflet-container {
height: 100%;
- img.leaflet-tile {
- -webkit-filter: contrast(120%) brightness(115%) grayscale(95%); /* Webkit */
- filter: gray; /* IE6-9 */
- filter: contrast(120%) brightness(115%) grayscale(95%); /* W3C */
- }
+ // img.leaflet-tile {
+ // -webkit-filter: contrast(120%) brightness(115%) grayscale(95%); /* Webkit */
+ // filter: gray; /* IE6-9 */
+ // filter: contrast(120%) brightness(115%) grayscale(95%); /* W3C */
+ // }
}
&.hidden {
@@ -65,14 +65,14 @@
}
}
- .sites-layer {
+ .sites-layer, .shapes-layer {
position: fixed;
top: 0px;
left: 110px;
}
&.narrative-mode {
- .sites-layer {
+ .sites-layer, .shapes-layer {
position: fixed;
top: 0px;
left: 0px;
diff --git a/src/selectors/index.js b/src/selectors/index.js
index 53cae3f..6494b4a 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -15,13 +15,17 @@ export const getSites = (state) => {
}
export const getSources = state => {
if (process.env.features.USE_SOURCES) return state.domain.sources
+ return {}
+}
+export const getShapes = state => {
+ if (process.env.features.USE_SHAPES) return state.domain.shapes
return []
}
export const getNotifications = state => state.domain.notifications
export const getTagTree = state => state.domain.tags
export const getTagsFilter = state => state.app.filters.tags
export const getCategoriesFilter = state => state.app.filters.categories
-export const getTimeRange = state => state.app.filters.timerange
+export const getTimeRange = state => state.app.timeline.range
/**
diff --git a/src/store/initial.js b/src/store/initial.js
index 9cb4a2a..32e5c6f 100644
--- a/src/store/initial.js
+++ b/src/store/initial.js
@@ -39,32 +39,48 @@ const initial = {
current: null
},
filters: {
- timerange: [
- new Date(2013, 2, 23, 12),
- new Date(2016, 2, 23, 12)
- ],
- mapBounds: null,
tags: [],
categories: [],
views: {
events: true,
- coevents: false,
routes: false,
sites: true
},
},
isMobile: (/Mobi/.test(navigator.userAgent)),
language: 'en-US',
- mapAnchor: [31.356397, 34.784818],
- zoomLevels: [
- { label: '3 years', duration: 1576800 },
- { label: '3 months', duration: 129600 },
- { label: '3 days', duration: 4320 },
- { label: '12 hours', duration: 720 },
- { label: '2 hours', duration: 120 },
- { label: '30 min', duration: 30 },
- { label: '10 min', duration: 10 }
- ],
+ map: {
+ anchor: [31.356397, 34.784818],
+ startZoom: 11,
+ minZoom: 7,
+ maxZoom: 18,
+ bounds: null,
+ maxBounds: [[180, -180], [-180, 180]]
+ },
+ timeline: {
+ dimensions: {
+ height: 140,
+ width: 0,
+ width_controls: 100,
+ height_controls: 115,
+ margin_left: 200,
+ margin_top: 20,
+ trackHeight: 80
+ },
+ range: [
+ new Date(2013, 2, 23, 12),
+ new Date(2016, 2, 23, 12)
+ ],
+ zoomLevels: [
+ { label: '3 years', duration: 1576800 },
+ { label: '3 months', duration: 129600 },
+ { label: '3 days', duration: 4320 },
+ { label: '12 hours', duration: 720 },
+ { label: '2 hours', duration: 120 },
+ { label: '30 min', duration: 30 },
+ { label: '10 min', duration: 10 }
+ ],
+ },
flags: {
isFetchingDomain: false,
isFetchingSources: false,
@@ -81,6 +97,7 @@ const initial = {
* as well as dom elements to attach SVG
*/
ui: {
+ tiles: 'openstreetmap', // ['openstreetmap', 'streets', 'satellite']
style: {
categories: {
default: '#f3de2c',
@@ -91,6 +108,13 @@ const initial = {
stroke: 'red',
strokeWidth: 3
}
+ },
+ shapes: {
+ default: {
+ stroke: 'blue',
+ strokeWidth: 3,
+ opacity: 0.9
+ }
}
},
dom: {
@@ -109,7 +133,7 @@ if (process.env.store) {
}
// 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.filters.timerange[1] = new Date(appStore.app.filters.timerange[1])
+appStore.app.timeline.range[0] = new Date(appStore.app.timeline.range[0])
+appStore.app.timeline.range[1] = new Date(appStore.app.timeline.range[1])
export default appStore