From e93d7d88318fbec1795e49d90aac2cf837d09f90 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 19 May 2020 14:08:03 +0200 Subject: [PATCH] rewrite features as part of store + add HIGHLIGHT_GROUPS --- example.config.js | 23 ++++---- src/actions/index.js | 13 ++--- src/components/Layout.js | 6 +-- src/components/Map.jsx | 14 ++--- src/components/Timeline.jsx | 11 +++- src/components/Toolbar/BottomActions.js | 4 +- src/components/Toolbar/Layout.js | 11 ++-- .../presentational/Map/Narratives.js | 4 +- .../presentational/Timeline/DatetimeBar.js | 44 ++++++++++----- .../presentational/Timeline/Events.js | 23 ++++---- src/reducers/features.js | 7 +++ src/reducers/index.js | 4 +- src/reducers/schema/categorySchema.js | 3 +- src/scss/toolbar.scss | 1 + src/selectors/index.js | 54 +++++++++++-------- src/store/initial.js | 13 +++++ 16 files changed, 151 insertions(+), 84 deletions(-) create mode 100644 src/reducers/features.js diff --git a/example.config.js b/example.config.js index dcb4618..9eac7ba 100644 --- a/example.config.js +++ b/example.config.js @@ -11,18 +11,6 @@ module.exports = { SHAPES_EXT: '/api/example/export_shapes/columns', INCOMING_DATETIME_FORMAT: '%m/%d/%YT%H:%M', // MAPBOX_TOKEN: 'pk.YOUR_MAPBOX_TOKEN', - features: { - USE_COVER: false, - USE_TAGS: false, - USE_SEARCH: false, - USE_SITES: true, - USE_SOURCES: true, - USE_SHAPES: false, - CATEGORIES_AS_TAGS: true, - /** setting this to true will 'graph' non-located events. TODO: document - * and rename **/ - ASSOCIATIVE_EVENTS_BY_TAG: false - }, store: { app: { map: { @@ -40,6 +28,17 @@ module.exports = { selectedEvent: {} // tiles: 'your-mapbox-account-name/x5678-map-id' } + }, + features: { + USE_COVER: false, + USE_TAGS: false, + USE_SEARCH: false, + USE_SITES: true, + USE_SOURCES: true, + USE_SHAPES: false, + CATEGORIES_AS_TAGS: true, + GRAPH_NONLOCATED: false, + HIGHLIGHT_GROUPS: false } } } diff --git a/src/actions/index.js b/src/actions/index.js index 438584d..dc14b2a 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -23,7 +23,8 @@ export function fetchDomain () { return [] } - return dispatch => { + return (dispatch, getState) => { + const features = getState().features dispatch(toggleFetchingDomain()) const eventPromise = fetch(EVENT_DATA_URL) @@ -35,21 +36,21 @@ export function fetchDomain () { .catch(() => handleError(domainMsg('categories'))) let narPromise = Promise.resolve([]) - if (process.env.features.USE_NARRATIVES) { + if (features.USE_NARRATIVES) { narPromise = fetch(NARRATIVE_URL) .then(response => response.json()) .catch(() => handleError(domainMsg('narratives'))) } let sitesPromise = Promise.resolve([]) - if (process.env.features.USE_SITES) { + if (features.USE_SITES) { sitesPromise = fetch(SITES_URL) .then(response => response.json()) .catch(() => handleError(domainMsg('sites'))) } let tagsPromise = Promise.resolve([]) - if (process.env.features.USE_TAGS) { + if (features.USE_TAGS) { if (!TAGS_URL) { tagsPromise = Promise.resolve(handleError('USE_TAGS is true, but you have not provided a TAGS_EXT')) } else { @@ -60,7 +61,7 @@ export function fetchDomain () { } let sourcesPromise = Promise.resolve([]) - if (process.env.features.USE_SOURCES) { + if (features.USE_SOURCES) { if (!SOURCES_URL) { sourcesPromise = Promise.resolve(handleError('USE_SOURCES is true, but you have not provided a SOURCES_EXT')) } else { @@ -71,7 +72,7 @@ export function fetchDomain () { } let shapesPromise = Promise.resolve([]) - if (process.env.features.USE_SHAPES) { + if (features.USE_SHAPES) { shapesPromise = fetch(SHAPES_URL) .then(response => response.json()) .catch(() => handleError(domainMsg('shapes'))) diff --git a/src/components/Layout.js b/src/components/Layout.js index 0df780f..73017af 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -108,12 +108,12 @@ class Dashboard extends React.Component { } render () { - const { actions, app, domain, ui } = this.props + const { actions, app, domain, ui, features } = this.props if (isMobile || window.innerWidth < 1000) { return (
- {process.env.features.USE_COVER && ( + {features.USE_COVER && ( {/* enable USE_COVER in config.js features, and customise your header */} {/* pass 'actions.toggleCover' as a prop to your custom header */} @@ -193,7 +193,7 @@ class Dashboard extends React.Component { } /> ) : null} - {process.env.features.USE_COVER && ( + {features.USE_COVER && ( {/* enable USE_COVER in config.js features, and customise your header */} {/* pass 'actions.toggleCover' as a prop to your custom header */} diff --git a/src/components/Map.jsx b/src/components/Map.jsx index ae340bc..d73946d 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -34,7 +34,7 @@ class Map extends React.Component { componentDidMount () { if (this.map === null) { - this.initializeMap() + // this.initializeMap() } } @@ -139,7 +139,7 @@ class Map extends React.Component { const pane = this.map.getPanes().overlayPane const { width, height } = this.getClientDims() - return ( + return !!this.map ? ( - ) + ) : null } renderSites () { @@ -183,6 +183,7 @@ class Map extends React.Component { styles={this.props.ui.narratives} onSelect={this.props.methods.onSelect} onSelectNarrative={this.props.methods.onSelectNarrative} + features={this.props.features} /> ) } @@ -266,8 +267,8 @@ function mapStateToProps (state) { locations: selectors.selectLocations(state), narratives: selectors.selectNarratives(state), categories: selectors.getCategories(state), - sites: selectors.getSites(state), - shapes: selectors.getShapes(state) + sites: selectors.selectSites(state), + shapes: selectors.selectShapes(state) }, app: { views: state.app.filters.views, @@ -285,7 +286,8 @@ function mapStateToProps (state) { narratives: state.ui.style.narratives, mapSelectedEvents: state.ui.style.selectedEvents, shapes: state.ui.style.shapes - } + }, + features: selectors.getFeatures(state) } } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 928e600..8852398 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -287,6 +287,7 @@ class Timeline extends React.Component { const heightStyle = { height: dims.height } const extraStyle = { ...heightStyle, ...foldedStyle } const contentHeight = { height: dims.contentHeight } + const { categories } = this.props.domain return (
@@ -347,10 +348,17 @@ class Timeline extends React.Component { narrative={this.props.app.narrative} getDatetimeX={this.getDatetimeX} getCategoryY={this.state.scaleY} + getHighlights={group => { + if (group === 'None') { + return [] + } + return categories.map(c => c.group === group) + }} getCategoryColor={this.props.methods.getCategoryColor} transitionDuration={this.state.transitionDuration} onSelect={this.props.methods.onSelect} dims={dims} + features={this.props.features} />
@@ -378,7 +386,8 @@ function mapStateToProps (state) { ui: { dom: state.ui.dom, styles: state.ui.style.selectedEvents - } + }, + features: selectors.getFeatures(state) } } diff --git a/src/components/Toolbar/BottomActions.js b/src/components/Toolbar/BottomActions.js index 9ccfb8d..58a5b2c 100644 --- a/src/components/Toolbar/BottomActions.js +++ b/src/components/Toolbar/BottomActions.js @@ -8,7 +8,7 @@ function BottomActions (props) { function renderToggles () { return [
- {process.env.features.USE_SITES ? : null} @@ -20,7 +20,7 @@ function BottomActions (props) { />
,
- {process.env.features.USE_COVER ? : null}
diff --git a/src/components/Toolbar/Layout.js b/src/components/Toolbar/Layout.js index ffc89b3..817dc91 100644 --- a/src/components/Toolbar/Layout.js +++ b/src/components/Toolbar/Layout.js @@ -32,7 +32,7 @@ class Toolbar extends React.Component { } renderSearch () { - if (process.env.features.USE_SEARCH) { + if (this.props.features.USE_SEARCH) { return ( @@ -154,7 +154,7 @@ class Toolbar extends React.Component { const tagsLabel = copy[this.props.language].toolbar.tags_label const categoriesLabel = 'Categories' // TODO: const isTags = this.props.tags && this.props.tags.children - const isCategories = process.env.features.CATEGORIES_AS_TAGS + const isCategories = this.props.features.CATEGORIES_AS_TAGS return (
@@ -176,6 +176,7 @@ class Toolbar extends React.Component { cover={{ toggle: this.props.actions.toggleCover }} + features={this.props.features} />
) @@ -195,6 +196,7 @@ class Toolbar extends React.Component { function mapStateToProps (state) { return { + features: selectors.getFeatures(state), tags: selectors.getTagTree(state), categories: selectors.getCategories(state), narratives: selectors.selectNarratives(state), @@ -202,7 +204,6 @@ function mapStateToProps (state) { activeTags: selectors.getActiveTags(state), activeCategories: selectors.getActiveCategories(state), viewFilters: state.app.filters.views, - features: state.app.features, narrative: state.app.narrative, sitesShowing: state.app.flags.isShowingSites, infoShowing: state.app.flags.isInfopopup diff --git a/src/components/presentational/Map/Narratives.js b/src/components/presentational/Map/Narratives.js index b48fe8a..7cf73fe 100644 --- a/src/components/presentational/Map/Narratives.js +++ b/src/components/presentational/Map/Narratives.js @@ -10,7 +10,7 @@ const defaultStyles = { stroke: 'none' } -function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) { +function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint, features }) { function getNarrativeStyle (narrativeId) { const styleName = (narrativeId && narrativeId in styles) ? narrativeId @@ -153,7 +153,7 @@ function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, return ( - {(process.env.features.NARRATIVE_STEP_STYLES + {(features.NARRATIVE_STEP_STYLES ? renderBetweenMarked(n) : renderFullNarrative(n) )} diff --git a/src/components/presentational/Timeline/DatetimeBar.js b/src/components/presentational/Timeline/DatetimeBar.js index 9660baf..3cb668b 100644 --- a/src/components/presentational/Timeline/DatetimeBar.js +++ b/src/components/presentational/Timeline/DatetimeBar.js @@ -1,7 +1,7 @@ import React from 'react' export default ({ - category, + highlights, events, x, y, @@ -10,14 +10,34 @@ export default ({ onSelect, styleProps, extraRender -}) => ( - -) +}) => { + if (highlights.length === 0) { + return ( + + ) + } + const sectionHeight = height / highlights.length + return ( + + {highlights.map((h, idx) => ( + + ))} + + ) +} diff --git a/src/components/presentational/Timeline/Events.js b/src/components/presentational/Timeline/Events.js index 2184e39..444b97c 100644 --- a/src/components/presentational/Timeline/Events.js +++ b/src/components/presentational/Timeline/Events.js @@ -7,8 +7,6 @@ import Project from './Project' import { calcOpacity } from '../../../common/utilities' import { sizes } from '../../../common/global' -const GRAPH_NONLOCATED = 'GRAPH_NONLOCATED' in process.env.features && process.env.features.GRAPH_NONLOCATED - function renderDot (event, styles, props) { return = 0 ? styles.opacity : 0.05 : 0.6 @@ -35,6 +33,7 @@ function renderBar (event, styles, props) { width={sizes.eventDotR / 4} height={props.dims.trackHeight} styleProps={{ ...styles, fillOpacity }} + highlights={props.highlights} /> } @@ -66,10 +65,11 @@ const TimelineEvents = ({ getDatetimeX, getCategoryY, getCategoryColor, + getHighlights, onSelect, transitionDuration, - // styleDatetime, - dims + dims, + features }) => { const narIds = narrative ? narrative.steps.map(s => s.id) : [] @@ -91,25 +91,28 @@ const TimelineEvents = ({ } } - const colour = event.colour ? event.colour : getCategoryColor(event.category) + let colour = event.colour ? event.colour : getCategoryColor(event.category) const styles = { fill: colour, fillOpacity: calcOpacity(1), transition: `transform ${transitionDuration / 1000}s ease` } + return renderShape(event, styles, { x: getDatetimeX(event.timestamp), - y: (GRAPH_NONLOCATED && !event.latitude && !event.longitude) + y: (features.GRAPH_NONLOCATED && !event.latitude && !event.longitude) ? event.projectOffset >= 0 ? dims.trackHeight - event.projectOffset : dims.marginTop - : getCategoryY(event.category), + : getCategoryY ? getCategoryY(event.category) : () => null, onSelect: () => onSelect([event]), - dims + dims, + highlights: features.HIGHLIGHT_GROUPS ? getHighlights(event.tags[0]) : [], + features }) } /* set `renderProjects` */ let renderProjects = () => null - if (GRAPH_NONLOCATED) { + if (features.GRAPH_NONLOCATED) { renderProjects = function () { return {projects.map(project => state.domain.events @@ -11,18 +10,9 @@ export const getNarratives = state => state.domain.narratives export const getActiveNarrative = state => state.app.narrative export const getActiveStep = state => state.app.narrativeState.current export const getSelected = state => state.app.selected -export const getSites = (state) => { - if (process.env.features.USE_SITES) return state.domain.sites.filter(s => !!(+s.enabled)) - return [] -} -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 getSites = state => state.domain.sites +export const getSources = state => state.domain.sources +export const getShapes = state => state.domain.shapes export const getNotifications = state => state.domain.notifications export const getTagTree = state => state.domain.tags export const getActiveTags = state => state.app.filters.tags @@ -30,6 +20,24 @@ export const getActiveCategories = state => state.app.filters.categories export const getTimeRange = state => state.app.timeline.range export const getTimelineDimensions = state => state.app.timeline.dimensions export const selectNarrative = state => state.app.narrative +export const getFeatures = state => state.features + +export const selectSites = createSelector([getSites, getFeatures], (sites, features) => { + if (features.USE_SITES) { + return sites.filter(s => !!(+s.enabled)) + } + return [] +}) + +export const selectSources = createSelector([getSources, getFeatures], (sources, features) => { + if (features.USE_SOURCES) return sources + return {} +}) + +export const selectShapes = createSelector([getShapes, getFeatures], (shapes, features) => { + if (features.USE_SHAPES) return shapes + return [] +}) /** * Of all available events, selects those that @@ -38,14 +46,14 @@ export const selectNarrative = state => state.app.narrative * 3. exist in an active category */ export const selectEvents = createSelector( - [getEvents, getActiveTags, getActiveCategories, getTimeRange], - (events, activeTags, activeCategories, timeRange) => { + [getEvents, getActiveTags, getActiveCategories, getTimeRange, getFeatures], + (events, activeTags, activeCategories, timeRange, features) => { return events.reduce((acc, event) => { const isMatchingTag = (event.tags && event.tags.map(tag => activeTags.includes(tag)).some(s => s)) || activeTags.length === 0 const isActiveTag = isMatchingTag || activeTags.length === 0 const isActiveCategory = activeCategories.includes(event.category) || activeCategories.length === 0 let isActiveTime = isTimeRangedIn(event, timeRange) - isActiveTime = GRAPH_NONLOCATED ? ((!event.latitude && !event.longitude) || isActiveTime) : isActiveTime + isActiveTime = features.GRAPH_NONLOCATED ? ((!event.latitude && !event.longitude) || isActiveTime) : isActiveTime if (isActiveTime && isActiveTag && isActiveCategory) { acc[event.id] = { ...event } @@ -60,9 +68,9 @@ export const selectEvents = createSelector( * and if TAGS are being used, select them if their tags are enabled */ export const selectNarratives = createSelector( - [getEvents, getNarratives, getSources], - (events, narrativesMeta, sources) => { - if (!process.env.features.USE_NARRATIVES) { + [getEvents, getNarratives, getSources, getFeatures], + (events, narrativesMeta, sources, features) => { + if (!features.USE_NARRATIVES) { return [] } const narratives = {} @@ -162,9 +170,9 @@ export const selectProjectedEvents = createSelector( } */ export const selectEventsAndProjects = createSelector( - [selectEvents], - events => { - if (!GRAPH_NONLOCATED) { + [selectEvents, getFeatures], + (events, features) => { + if (!features.GRAPH_NONLOCATED) { return [events, []] } diff --git a/src/store/initial.js b/src/store/initial.js index 9274e49..978940a 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -127,6 +127,19 @@ const initial = { timeslider: 'timeslider', map: 'map' } + }, + + features: { + CATEGORIES_AS_TAGS: true, + USE_COVER: false, + USE_TAGS: false, + USE_SEARCH: false, + USE_SITES: false, + USE_SOURCES: false, + USE_SHAPES: false, + USE_NARRATIVES: false, + GRAPH_NONLOCATED: false, + HIGHLIGHT_GROUPS: false } }