From 5560b09d7b5fb4dc07cfc0171195e40c128d9307 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 17 Jan 2019 15:21:31 +0000 Subject: [PATCH 01/15] move timeline dimensions to store --- src/components/Timeline.jsx | 25 ++++++++-------- src/components/presentational/TimelineClip.js | 4 +-- src/store/initial.js | 29 +++++++++++++------ 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 8d7104b..c950fb5 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -24,15 +24,7 @@ class Timeline extends React.Component { 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.dims, scaleX: null, scaleY: null, timerange: [null, null], @@ -117,9 +109,15 @@ class Timeline extends React.Component { 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() + }) + }); } } @@ -329,9 +327,10 @@ function mapStateToProps(state) { }, app: { timerange: selectors.getTimeRange(state), + dims: state.app.timeline.dimensions, selected: state.app.selected, language: state.app.language, - zoomLevels: state.app.zoomLevels, + zoomLevels: state.app.timeline.zoomLevels, narrative: state.app.narrative }, ui: { diff --git a/src/components/presentational/TimelineClip.js b/src/components/presentational/TimelineClip.js index 29491da..11f4c79 100644 --- a/src/components/presentational/TimelineClip.js +++ b/src/components/presentational/TimelineClip.js @@ -3,8 +3,8 @@ import React from 'react'; const TimelineClip = ({ dims }) => ( diff --git a/src/store/initial.js b/src/store/initial.js index 9cb4a2a..b30daea 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -56,15 +56,26 @@ const initial = { 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 } - ], + timeline: { + dimensions: { + height: 140, + width: 0, + width_controls: 100, + height_controls: 115, + margin_left: 200, + margin_top: 20, + trackHeight: 80 + }, + 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, From 9e4b9b2a1ff0d69a2745d61ca35c50475e6e182f Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 17 Jan 2019 15:25:58 +0000 Subject: [PATCH 02/15] move handles to dateline --- src/components/presentational/TimelineHandles.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/presentational/TimelineHandles.js b/src/components/presentational/TimelineHandles.js index 3e65f9b..12e9da9 100644 --- a/src/components/presentational/TimelineHandles.js +++ b/src/components/presentational/TimelineHandles.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; From 9d6d51e11d0d434690b3094ddabdd962cf2013bf Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 17 Jan 2019 17:40:47 +0000 Subject: [PATCH 03/15] load shape data from ext --- src/actions/index.js | 43 ++++++++++------ src/reducers/schema/shapeSchema.js | 8 +++ src/reducers/utils/validators.js | 78 +++++++++++++++++++----------- 3 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 src/reducers/schema/shapeSchema.js 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/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..f52f909 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,46 @@ 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) + + sanitizedDomain.shapes = sanitizedDomain.shapes.map(shape => { + const points = shape.items.map(coords => { + const _coords = coords.replace(/\s/g, '').split(',') + return { + lat: parseFloat(_coords[0]), + lon: parseFloat(_coords[1]) + } + }) + return { + name: shape.name, + points + } + }) + + console.log(sanitizedDomain) // 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 +159,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 } From 6226dc3e85053592f7f776186a2845c80d1f258d Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 17 Jan 2019 18:00:28 +0000 Subject: [PATCH 04/15] :lipstick: --- src/components/Map.jsx | 8 ++++---- src/components/SourceOverlay.jsx | 4 ++-- src/reducers/utils/validators.js | 3 --- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 8b0dd40..1c37b53 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -35,7 +35,7 @@ class Map extends React.Component { } componentWillReceiveProps(nextProps) { - // Set appropriate zoom for narrative + // 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); @@ -43,13 +43,13 @@ class Map extends React.Component { 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; - + if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) { this.map.setView([eventPoint.latitude, eventPoint.longitude]); } - } + } } - } + } initializeMap() { /** 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)}>
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/reducers/utils/validators.js b/src/reducers/utils/validators.js index f52f909..057d048 100644 --- a/src/reducers/utils/validators.js +++ b/src/reducers/utils/validators.js @@ -133,9 +133,6 @@ export function validateDomain (domain) { } }) - console.log(sanitizedDomain) - - // Message the number of failed items in domain Object.keys(discardedDomain).forEach(disc => { const len = discardedDomain[disc].length From fb46948b77f38f66814de61bf682fbb477252123 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 10:41:06 +0000 Subject: [PATCH 05/15] add MapShapes, analog of MapSites --- src/components/Map.jsx | 33 ++++++++++++++++++++++------ src/components/MapShapes.jsx | 33 ++++++++++++++++++++++++++++ src/components/MapSites.jsx | 37 +++++++++++++++----------------- src/reducers/utils/validators.js | 19 ++++++---------- src/scss/map.scss | 4 ++-- src/selectors/index.js | 4 ++++ 6 files changed, 89 insertions(+), 41 deletions(-) create mode 100644 src/components/MapShapes.jsx diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 1c37b53..c8c0a4e 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -10,6 +10,7 @@ import 'leaflet'; import { isNotNullNorUndefined } from '../js/utilities'; import MapSites from './MapSites.jsx'; +import MapShapes from './MapShapes.jsx'; import MapEvents from './MapEvents.jsx'; import MapSelectedEvents from './MapSelectedEvents.jsx'; import MapNarratives from './MapNarratives.jsx'; @@ -147,6 +148,17 @@ class Map extends React.Component { ); } + renderShapes() { + return ( + + ) + } + renderNarratives() { return ( + {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,7 +262,8 @@ 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, diff --git a/src/components/MapShapes.jsx b/src/components/MapShapes.jsx new file mode 100644 index 0000000..19a842c --- /dev/null +++ b/src/components/MapShapes.jsx @@ -0,0 +1,33 @@ +import React from 'react'; + +function MapShapes({ map, shapes, mapTransformX, mapTransformY }) { + function projectPoint(location) { + const latLng = new L.LatLng(location[0], location[1]); + return { + x: map.latLngToLayerPoint(latLng).x + mapTransformX, + y: map.latLngToLayerPoint(latLng).y + mapTransformY + }; + } + + function renderShape(shape) { + const coords = shape.points.map(projectPoint) + return coords.map(pt => ( +
+ {shape.name} +
+ )); + } + + if (!shapes || !shapes.length) return null; + + return ( +
+ {shapes.map(renderShape)} +
+ ) + +} + +export default MapShapes; diff --git a/src/components/MapSites.jsx b/src/components/MapSites.jsx index 16d4a92..948d883 100644 --- a/src/components/MapSites.jsx +++ b/src/components/MapSites.jsx @@ -1,36 +1,33 @@ import React from 'react'; -class MapSites extends React.Component { - - projectPoint(location) { +function MapSites({ map, sites, mapTransformX, mapTransformY }) { + function 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 + x: map.latLngToLayerPoint(latLng).x + mapTransformX, + y: map.latLngToLayerPoint(latLng).y + mapTransformY }; } - renderSite(site) { - const { x, y } = this.projectPoint([site.latitude, site.longitude]); + function renderSite(site) { + const { x, y } = projectPoint([site.latitude, site.longitude]); return (
- {site.site} -
+ className="leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top" + style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)`}}> + {site.site} +
); } - render () { - if (!this.props.sites || !this.props.sites.length) return
; + if (!sites || !sites.length) return null; - return ( -
- {this.props.sites.map(site => { return this.renderSite(site); })} -
- ) - } + return ( +
+ {sites.map(renderSite)} +
+ ) } -export default MapSites; \ No newline at end of file +export default MapSites; diff --git a/src/reducers/utils/validators.js b/src/reducers/utils/validators.js index 057d048..cbd13fd 100644 --- a/src/reducers/utils/validators.js +++ b/src/reducers/utils/validators.js @@ -119,19 +119,14 @@ export function validateDomain (domain) { validateObject(domain.sources, 'sources', sourceSchema) validateObject(domain.shapes, 'shapes', shapeSchema) - sanitizedDomain.shapes = sanitizedDomain.shapes.map(shape => { - const points = shape.items.map(coords => { - const _coords = coords.replace(/\s/g, '').split(',') - return { - lat: parseFloat(_coords[0]), - lon: parseFloat(_coords[1]) - } - }) - return { + // NB: [lat, lon] array is best format for projecting into map + sanitizedDomain.shapes = sanitizedDomain.shapes.map(shape => ({ name: shape.name, - points - } - }) + points: shape.items.map(coords => ( + coords.replace(/\s/g, '').split(',') + )) + }) + ) // Message the number of failed items in domain Object.keys(discardedDomain).forEach(disc => { diff --git a/src/scss/map.scss b/src/scss/map.scss index a1d90b2..5d47661 100644 --- a/src/scss/map.scss +++ b/src/scss/map.scss @@ -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..e9dfab2 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -15,6 +15,10 @@ 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 From f561064e6c0cd3c6aa1466882c72bbac53edae90 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 11:19:40 +0000 Subject: [PATCH 06/15] shapes -> lines between points --- src/components/Map.jsx | 1 + src/components/MapNarratives.jsx | 1 + src/components/MapShapes.jsx | 59 ++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index c8c0a4e..44dd942 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -151,6 +151,7 @@ class Map extends React.Component { renderShapes() { return ( {steps.map((s, idx) => this.renderNarrativeStep(idx, n))} diff --git a/src/components/MapShapes.jsx b/src/components/MapShapes.jsx index 19a842c..087d422 100644 --- a/src/components/MapShapes.jsx +++ b/src/components/MapShapes.jsx @@ -1,33 +1,56 @@ -import React from 'react'; +import React from 'react' +import { Portal } from 'react-portal' -function MapShapes({ map, shapes, mapTransformX, mapTransformY }) { +function MapShapes({ svg, map, shapes, mapTransformX, mapTransformY }) { function projectPoint(location) { - const latLng = new L.LatLng(location[0], location[1]); + const latLng = new L.LatLng(location[0], location[1]) return { x: map.latLngToLayerPoint(latLng).x + mapTransformX, y: map.latLngToLayerPoint(latLng).y + mapTransformY - }; + } } - function renderShape(shape) { - const coords = shape.points.map(projectPoint) - return coords.map(pt => ( -
- {shape.name} -
- )); + function renderShape(shape, lineStyle) { + const lineCoords = [] + const points = shape.points + .map(projectPoint) + + points.forEach((p1, idx) => { + 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 => ( + + + )) } - if (!shapes || !shapes.length) return null; + if (!shapes || !shapes.length) return null return ( -
- {shapes.map(renderShape)} -
+ + + {shapes.map(s => renderShape(s, { + strokeWidth: 3, + stroke: 'blue' + }))} + + ) } -export default MapShapes; +export default MapShapes From e7cac13fb5eb84c4cddeeb06e0c3737b18320236 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 11:51:00 +0000 Subject: [PATCH 07/15] significantly refactor presentational components --- .gitignore | 2 + src/components/.DS_Store | Bin 0 -> 6148 bytes src/components/Card.jsx | 16 ++--- src/components/Dashboard.jsx | 2 +- src/components/Map.jsx | 41 ++++++----- src/components/Timeline.jsx | 14 ++-- src/components/presentational/.DS_Store | Bin 0 -> 6148 bytes .../{CardCaret.js => Card/Caret.js} | 0 .../{CardCategory.js => Card/Category.js} | 2 +- .../{CardLocation.js => Card/Location.js} | 4 +- .../{CardNarrative.js => Card/Narrative.js} | 2 +- .../NarrativeLink.js} | 0 .../{CardSource.js => Card/Source.js} | 4 +- .../{CardSummary.js => Card/Summary.js} | 2 +- .../{CardTags.js => Card/Tags.js} | 2 +- .../{CardTimestamp.js => Card/Timestamp.js} | 4 +- .../Map/DefsMarkers.jsx} | 0 .../Map/Events.jsx} | 43 +++++------- .../Map/Narratives.jsx} | 64 +++++++----------- .../Map/SelectedEvents.jsx} | 13 +--- .../Map/Shapes.jsx} | 10 +-- .../Map/Sites.jsx} | 10 +-- .../Adjust.js} | 0 .../{NarrativeCard.js => Narrative/Card.js} | 2 +- .../{NarrativeClose.js => Narrative/Close.js} | 0 .../Controls.js} | 14 ++-- .../{TimelineClip.js => Timeline/Clip.js} | 0 .../{ => Timeline}/DatetimeDot.js | 0 .../{TimelineEvents.js => Timeline/Events.js} | 0 .../Handles.js} | 0 .../{TimelineHeader.js => Timeline/Header.js} | 0 .../{TimelineLabels.js => Timeline/Labels.js} | 2 +- .../Markers.js} | 0 .../ZoomControls.js} | 0 34 files changed, 103 insertions(+), 150 deletions(-) create mode 100644 src/components/.DS_Store create mode 100644 src/components/presentational/.DS_Store rename src/components/presentational/{CardCaret.js => Card/Caret.js} (100%) rename src/components/presentational/{CardCategory.js => Card/Category.js} (84%) rename src/components/presentational/{CardLocation.js => Card/Location.js} (84%) rename src/components/presentational/{CardNarrative.js => Card/Narrative.js} (86%) rename src/components/presentational/{CardNarrativeLink.js => Card/NarrativeLink.js} (100%) rename src/components/presentational/{CardSource.js => Card/Source.js} (96%) rename src/components/presentational/{CardSummary.js => Card/Summary.js} (88%) rename src/components/presentational/{CardTags.js => Card/Tags.js} (94%) rename src/components/presentational/{CardTimestamp.js => Card/Timestamp.js} (87%) rename src/components/{MapDefsMarkers.jsx => presentational/Map/DefsMarkers.jsx} (100%) rename src/components/{MapEvents.jsx => presentational/Map/Events.jsx} (55%) rename src/components/{MapNarratives.jsx => presentational/Map/Narratives.jsx} (50%) rename src/components/{MapSelectedEvents.jsx => presentational/Map/SelectedEvents.jsx} (66%) rename src/components/{MapShapes.jsx => presentational/Map/Shapes.jsx} (74%) rename src/components/{MapSites.jsx => presentational/Map/Sites.jsx} (63%) rename src/components/presentational/{NarrativeAdjust.js => Narrative/Adjust.js} (100%) rename src/components/presentational/{NarrativeCard.js => Narrative/Card.js} (94%) rename src/components/presentational/{NarrativeClose.js => Narrative/Close.js} (100%) rename src/components/presentational/{NarrativeControls.js => Narrative/Controls.js} (71%) rename src/components/presentational/{TimelineClip.js => Timeline/Clip.js} (100%) rename src/components/presentational/{ => Timeline}/DatetimeDot.js (100%) rename src/components/presentational/{TimelineEvents.js => Timeline/Events.js} (100%) rename src/components/presentational/{TimelineHandles.js => Timeline/Handles.js} (100%) rename src/components/presentational/{TimelineHeader.js => Timeline/Header.js} (100%) rename src/components/presentational/{TimelineLabels.js => Timeline/Labels.js} (94%) rename src/components/presentational/{TimelineMarkers.js => Timeline/Markers.js} (100%) rename src/components/presentational/{TimelineZoomControls.js => Timeline/ZoomControls.js} (100%) 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/src/components/.DS_Store b/src/components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 ); @@ -153,9 +160,7 @@ class Map extends React.Component { ) } @@ -165,9 +170,7 @@ class Map extends React.Component { ); } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index c950fb5..ac49152 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -5,14 +5,14 @@ import hash from 'object-hash'; import copy from '../js/data/copy.json'; import { formatterWithYear, parseDate } from '../js/utilities'; -import TimelineHeader from './presentational/TimelineHeader'; +import TimelineHeader from './presentational/Timeline/Header'; 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 TimelineClip from './presentational/Timeline/Clip'; +import TimelineHandles from './presentational/Timeline/Handles.js'; +import TimelineZoomControls from './presentational/Timeline/ZoomControls.js'; +import TimelineLabels from './presentational/Timeline/Labels.js'; +import TimelineMarkers from './presentational/Timeline/Markers.js' +import TimelineEvents from './presentational/Timeline/Events.js'; import TimelineCategories from './TimelineCategories.jsx'; class Timeline extends React.Component { diff --git a/src/components/presentational/.DS_Store b/src/components/presentational/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1e61c2573f391befc331f5146ef00e4f352b678a GIT binary patch literal 6148 zcmeHKu};G<5Ixfj0hUgP(ZA4*4XcC^mhu6hrfMq^8mblvvE&o@8h(J8ozLN&eYA<< ztO&uKboUb9o$vgl*f9~A@ire34T&g)GA2hb5n(@TM-F6!lMT;N%tw>ys;oMZI-m?F z1HX|0|KZaG-O-xz$oHFk`^r_8<=f4a=yP7Yyq#k)4PV~ts}`L-VL(@?S4FCcr&;U% zy)1hzY5{kZR_)`p+B%5SoEJ@DR&f@s2wa{BA3&+&~eF}^m g$Nqtj;!~&>_$)sFonRdhBM|)&@HD7V27Z)*FMIHPVE_OC literal 0 HcmV?d00001 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 6851fdd..e2ffbea 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 ({ narrativeProps, onSelectNarrative, svg, narrative, narratives, projectPoint }) { + function getNarrativeStyle(narrativeId) { + const styleName = (narrativeId && narrativeId in narrativeProps) ? narrativeId : 'default' - return this.props.narrativeProps[styleName] + return narrativeProps[styleName] } - getStepStyle(name) { + function getStepStyle(name) { if (name === 'None') return null - return this.props.narrativeProps.stepStyles[name] + return narrativeProps.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,26 +84,23 @@ class MapNarratives extends React.Component { } - renderNarrative(n) { + function renderNarrative(n) { const steps = n.steps.slice(0, n.steps.length - 1) - console.log(n) 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 (!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 100% rename from src/components/presentational/TimelineClip.js rename to src/components/presentational/Timeline/Clip.js 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 100% rename from src/components/presentational/TimelineHandles.js rename to src/components/presentational/Timeline/Handles.js 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/TimelineLabels.js b/src/components/presentational/Timeline/Labels.js similarity index 94% rename from src/components/presentational/TimelineLabels.js rename to src/components/presentational/Timeline/Labels.js index 36466ba..9a75743 100644 --- a/src/components/presentational/TimelineLabels.js +++ b/src/components/presentational/Timeline/Labels.js @@ -1,6 +1,6 @@ import React from 'react'; -import { formatterWithYear } from '../../js/utilities.js'; +import { formatterWithYear } from '../../../js/utilities.js'; const TimelineLabels = ({ dims, timelabels }) => { 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 From 10b62e1e23e44e0ea2b5d3ff365032f1a1df3cd4 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 12:09:37 +0000 Subject: [PATCH 08/15] add optional styling for shapes from redux store --- src/components/Map.jsx | 31 ++++++++--------- .../presentational/Map/Narratives.jsx | 8 ++--- src/components/presentational/Map/Shapes.jsx | 33 ++++++++++--------- src/store/initial.js | 7 ++++ 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index c1df193..97a4160 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -9,15 +9,14 @@ import 'leaflet'; import { isNotNullNorUndefined } from '../js/utilities'; -import MapSites from './presentational/Map/Sites.jsx'; -import MapShapes from './presentational/Map/Shapes.jsx'; -import MapEvents from './presentational/Map/Events.jsx'; -import MapSelectedEvents from './presentational/Map/SelectedEvents.jsx'; -import MapNarratives from './presentational/Map/Narratives.jsx'; -import MapDefsMarkers from './presentational/Map/DefsMarkers.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'; class Map extends React.Component { - constructor() { super(); this.projectPoint = this.projectPoint.bind(this) @@ -147,7 +146,7 @@ class Map extends React.Component { renderSites() { return ( - ) } renderNarratives() { return ( - @@ -198,7 +198,7 @@ class Map extends React.Component { renderEvents() { return ( - - + ) } @@ -278,7 +278,8 @@ function mapStateToProps(state) { }, ui: { dom: state.ui.dom, - narratives: state.ui.style.narratives + narratives: state.ui.style.narratives, + shapes: state.ui.style.shapes } } } diff --git a/src/components/presentational/Map/Narratives.jsx b/src/components/presentational/Map/Narratives.jsx index e2ffbea..84452e8 100644 --- a/src/components/presentational/Map/Narratives.jsx +++ b/src/components/presentational/Map/Narratives.jsx @@ -1,17 +1,17 @@ import React from 'react' import { Portal } from 'react-portal' -function MapNarratives ({ narrativeProps, onSelectNarrative, svg, narrative, narratives, projectPoint }) { +function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) { function getNarrativeStyle(narrativeId) { - const styleName = (narrativeId && narrativeId in narrativeProps) + const styleName = (narrativeId && narrativeId in styles) ? narrativeId : 'default' - return narrativeProps[styleName] + return styles[styleName] } function getStepStyle(name) { if (name === 'None') return null - return narrativeProps.stepStyles[name] + return styles.stepStyles[name] } function hasNoLocation(step) { diff --git a/src/components/presentational/Map/Shapes.jsx b/src/components/presentational/Map/Shapes.jsx index 44514bc..5fab671 100644 --- a/src/components/presentational/Map/Shapes.jsx +++ b/src/components/presentational/Map/Shapes.jsx @@ -1,8 +1,8 @@ import React from 'react' import { Portal } from 'react-portal' -function MapShapes({ svg, shapes, projectPoint }) { - function renderShape(shape, lineStyle) { +function MapShapes({ svg, shapes, projectPoint, styles }) { + function renderShape(shape) { const lineCoords = [] const points = shape.points .map(projectPoint) @@ -19,15 +19,21 @@ function MapShapes({ svg, shapes, projectPoint }) { } }) - return lineCoords.map(coords => ( - - - )) + return lineCoords.map(coords => { + const shapeStyles = (shape.name in styles) + ? styles[shape.name] + : styles.default + + return ( + + + ) + }) } if (!shapes || !shapes.length) return null @@ -35,10 +41,7 @@ function MapShapes({ svg, shapes, projectPoint }) { return ( - {shapes.map(s => renderShape(s, { - strokeWidth: 3, - stroke: 'blue' - }))} + {shapes.map(renderShape)} ) diff --git a/src/store/initial.js b/src/store/initial.js index b30daea..9c3326a 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -102,6 +102,13 @@ const initial = { stroke: 'red', strokeWidth: 3 } + }, + shapes: { + default: { + stroke: 'blue', + strokeWidth: 3, + opacity: 0.9 + } } }, dom: { From 033ecdce8e84dec09d99edd34b9add3d1cd4d25a Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 12:14:44 +0000 Subject: [PATCH 09/15] update example.config --- example.config.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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: {} + } + } } } From 3ebf668d27603bccf033e47820cdc5dfe2cdd1a4 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 14:58:25 +0000 Subject: [PATCH 10/15] refactor timeline config to redux state --- src/components/Map.jsx | 124 +++++++++++++------------ src/components/Timeline.jsx | 179 ++++++++++++++++++------------------ src/reducers/app.js | 4 +- src/selectors/index.js | 2 +- src/store/initial.js | 24 +++-- 5 files changed, 171 insertions(+), 162 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 97a4160..aa40290 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -1,27 +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 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'; +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(); + super() this.projectPoint = this.projectPoint.bind(this) - this.svgRef = React.createRef(); - this.map = null; + this.svgRef = React.createRef() + this.map = null this.state = { mapTransformX: 0, mapTransformY: 0 @@ -31,22 +35,23 @@ 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); + 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]) } } } @@ -56,51 +61,50 @@ class Map extends React.Component { /** * 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/v4/${this.props.ui.tiles}/{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 = 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({ @@ -118,7 +122,7 @@ class Map extends React.Component { } 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, @@ -127,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 ( @@ -141,7 +145,7 @@ class Map extends React.Component { > - ); + ) } renderSites() { @@ -151,7 +155,7 @@ class Map extends React.Component { projectPoint={this.projectPoint} isEnabled={this.props.app.views.sites} /> - ); + ) } renderShapes() { @@ -176,7 +180,7 @@ class Map extends React.Component { onSelect={this.props.methods.onSelect} onSelectNarrative={this.props.methods.onSelectNarrative} /> - ); + ) } /** @@ -209,7 +213,7 @@ class Map extends React.Component { onSelectNarrative={this.props.methods.onSelectNarrative} getCategoryColor={this.props.methods.getCategoryColor} /> - ); + ) } renderSelected() { @@ -219,7 +223,7 @@ class Map extends React.Component { selected={this.props.app.selected} projectPoint={this.projectPoint} /> - ); + ) } @@ -234,7 +238,7 @@ 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()} @@ -252,7 +256,7 @@ class Map extends React.Component {
{innerMap}
- ); + ) } } @@ -269,14 +273,14 @@ function mapStateToProps(state) { 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, shapes: state.ui.style.shapes diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index ac49152..a7ebdba 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -1,90 +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/Timeline/Header'; -import TimelineAxis from './TimelineAxis.jsx'; -import TimelineClip from './presentational/Timeline/Clip'; -import TimelineHandles from './presentational/Timeline/Handles.js'; -import TimelineZoomControls from './presentational/Timeline/ZoomControls.js'; -import TimelineLabels from './presentational/Timeline/Labels.js'; -import TimelineMarkers from './presentational/Timeline/Markers.js' -import TimelineEvents from './presentational/Timeline/Events.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: props.app.dims, + 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() }) } } @@ -93,20 +93,20 @@ 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: { @@ -117,7 +117,7 @@ class Timeline extends React.Component { () => { this.setState({ scaleX: this.makeScaleX() }) - }); + }) } } @@ -126,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) + }) } /** @@ -162,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 }) } /** @@ -170,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) { @@ -243,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} />
@@ -263,43 +264,43 @@ 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) }} /> - - - -
- ); + ) } } @@ -326,11 +327,9 @@ function mapStateToProps(state) { narratives: state.domain.narratives }, app: { - timerange: selectors.getTimeRange(state), - dims: state.app.timeline.dimensions, selected: state.app.selected, language: state.app.language, - zoomLevels: state.app.timeline.zoomLevels, + timeline: state.app.timeline, narrative: state.app.narrative }, ui: { @@ -339,4 +338,4 @@ function mapStateToProps(state) { } } -export default connect(mapStateToProps)(Timeline); +export default connect(mapStateToProps)(Timeline) diff --git a/src/reducers/app.js b/src/reducers/app.js index 43e39aa..f17afac 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] diff --git a/src/selectors/index.js b/src/selectors/index.js index e9dfab2..6494b4a 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -25,7 +25,7 @@ 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 9c3326a..ad7f021 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -39,23 +39,24 @@ 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], + map: { + anchor: [31.356397, 34.784818], + startZoom: 10, + minZoom: 7, + maxZoom: 18, + bounds: null, + maxBounds: [[180, -180], [-180, 180]] + }, timeline: { dimensions: { height: 140, @@ -66,6 +67,10 @@ const initial = { 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 }, @@ -92,6 +97,7 @@ const initial = { * as well as dom elements to attach SVG */ ui: { + tiles: 'openstreetmap', // ['openstreetmap', 'streets', 'satellite'] style: { categories: { default: '#f3de2c', @@ -127,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 From a2434b36c06b0a4a1ca141c3c35d5f0aafbb45b9 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 15:08:42 +0000 Subject: [PATCH 11/15] add support for custom styled maps (mapbox) --- src/components/Map.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index aa40290..d5c8d85 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -77,7 +77,7 @@ class Map extends React.Component { ) } 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}` + `http://a.tiles.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}` ) } else { s = L.tileLayer( From cb254884ca1101d24b4c2516d5f5becca84677e8 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 16:14:41 +0000 Subject: [PATCH 12/15] only show sites toggle if USE_SITES --- src/components/ToolbarBottomActions.jsx | 8 ++++---- src/scss/map.scss | 10 +++++----- src/store/initial.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) 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/scss/map.scss b/src/scss/map.scss index 5d47661..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 { diff --git a/src/store/initial.js b/src/store/initial.js index ad7f021..32e5c6f 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -51,7 +51,7 @@ const initial = { language: 'en-US', map: { anchor: [31.356397, 34.784818], - startZoom: 10, + startZoom: 11, minZoom: 7, maxZoom: 18, bounds: null, From 238e220d9fb90acb166ee2c3e9f01991928f69a5 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 16:46:02 +0000 Subject: [PATCH 13/15] fix categories labels y position --- src/components/Timeline.jsx | 9 +++--- src/components/TimelineCategories.jsx | 6 +--- .../presentational/Timeline/Labels.js | 30 +++++++++---------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index a7ebdba..840f05f 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -275,6 +275,7 @@ class Timeline extends React.Component { /> { this.onDragStart() }} onDrag={() => { this.onDrag() }} onDragEnd={() => { this.onDragEnd() }} @@ -290,10 +291,10 @@ class Timeline extends React.Component { dims={dims} onApplyZoom={this.onApplyZoom} /> - + {/* */} + {category.category} diff --git a/src/components/presentational/Timeline/Labels.js b/src/components/presentational/Timeline/Labels.js index 9a75743..60e260e 100644 --- a/src/components/presentational/Timeline/Labels.js +++ b/src/components/presentational/Timeline/Labels.js @@ -22,21 +22,21 @@ const TimelineLabels = ({ dims, timelabels }) => { y2="20" > - {/* */} - {/* {formatterWithYear(timelabels[0])} */} - {/* */} - {/* */} - {/* {formatterWithYear(timelabels[1])} */} - {/* */} + + {formatterWithYear(timelabels[0])} + + + {formatterWithYear(timelabels[1])} +
) } From e671b3416c4d7da3f381a7a183b16ebe6d8851af Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 16:58:47 +0000 Subject: [PATCH 14/15] add support from name from config --- src/components/Toolbar.jsx | 55 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 27 deletions(-) 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 {
this.selectTab(-1)}>
- ); + ) } 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()}
- ); + ) } } From 38f7662ff6d318abcd07b6915d47d1dc6092f9fb Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 18 Jan 2019 17:47:28 +0000 Subject: [PATCH 15/15] correct updateTimerange reducer --- src/reducers/app.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/reducers/app.js b/src/reducers/app.js index f17afac..bc79b05 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -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