mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
rewrite features as part of store + add HIGHLIGHT_GROUPS
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
{process.env.features.USE_COVER && (
|
||||
{features.USE_COVER && (
|
||||
<StaticPage showing={app.flags.isCover}>
|
||||
{/* 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 && (
|
||||
<StaticPage showing={app.flags.isCover}>
|
||||
{/* enable USE_COVER in config.js features, and customise your header */}
|
||||
{/* pass 'actions.toggleCover' as a prop to your custom header */}
|
||||
|
||||
@@ -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 ? (
|
||||
<Portal node={pane}>
|
||||
<svg
|
||||
ref={this.svgRef}
|
||||
@@ -149,7 +149,7 @@ class Map extends React.Component {
|
||||
className='leaflet-svg'
|
||||
/>
|
||||
</Portal>
|
||||
)
|
||||
) : 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div className={classes} style={extraStyle}>
|
||||
@@ -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}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -378,7 +386,8 @@ function mapStateToProps (state) {
|
||||
ui: {
|
||||
dom: state.ui.dom,
|
||||
styles: state.ui.style.selectedEvents
|
||||
}
|
||||
},
|
||||
features: selectors.getFeatures(state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ function BottomActions (props) {
|
||||
function renderToggles () {
|
||||
return [
|
||||
<div className='bottom-action-block'>
|
||||
{process.env.features.USE_SITES ? <SitesIcon
|
||||
{props.features.USE_SITES ? <SitesIcon
|
||||
isActive={props.sites.enabled}
|
||||
onClickHandler={props.sites.toggle}
|
||||
/> : null}
|
||||
@@ -20,7 +20,7 @@ function BottomActions (props) {
|
||||
/>
|
||||
</div>,
|
||||
<div className='botttom-action-block'>
|
||||
{process.env.features.USE_COVER ? <CoverIcon
|
||||
{props.features.USE_COVER ? <CoverIcon
|
||||
onClickHandler={props.cover.toggle}
|
||||
/> : null}
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
|
||||
renderSearch () {
|
||||
if (process.env.features.USE_SEARCH) {
|
||||
if (this.props.features.USE_SEARCH) {
|
||||
return (
|
||||
<TabPanel>
|
||||
<Search
|
||||
@@ -73,7 +73,7 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
|
||||
renderToolbarCategoriesPanel () {
|
||||
if (process.env.features.CATEGORIES_AS_TAGS) {
|
||||
if (this.props.features.CATEGORIES_AS_TAGS) {
|
||||
return (
|
||||
<TabPanel>
|
||||
<CategoriesListPanel
|
||||
@@ -88,7 +88,7 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
|
||||
renderToolbarTagPanel () {
|
||||
if (process.env.features.USE_TAGS &&
|
||||
if (this.props.features.USE_TAGS &&
|
||||
this.props.tags.children) {
|
||||
return (
|
||||
<TabPanel>
|
||||
@@ -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 (
|
||||
<div className='toolbar'>
|
||||
@@ -176,6 +176,7 @@ class Toolbar extends React.Component {
|
||||
cover={{
|
||||
toggle: this.props.actions.toggleCover
|
||||
}}
|
||||
features={this.props.features}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<g id={narrativeId} className='narrative'>
|
||||
{(process.env.features.NARRATIVE_STEP_STYLES
|
||||
{(features.NARRATIVE_STEP_STYLES
|
||||
? renderBetweenMarked(n)
|
||||
: renderFullNarrative(n)
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default ({
|
||||
category,
|
||||
highlights,
|
||||
events,
|
||||
x,
|
||||
y,
|
||||
@@ -10,14 +10,34 @@ export default ({
|
||||
onSelect,
|
||||
styleProps,
|
||||
extraRender
|
||||
}) => (
|
||||
<rect
|
||||
onClick={onSelect}
|
||||
className='event'
|
||||
x={x}
|
||||
y={y}
|
||||
style={styleProps}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
)
|
||||
}) => {
|
||||
if (highlights.length === 0) {
|
||||
return (
|
||||
<rect
|
||||
onClick={onSelect}
|
||||
className='event'
|
||||
x={x}
|
||||
y={y}
|
||||
style={styleProps}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const sectionHeight = height / highlights.length
|
||||
return (
|
||||
<React.Fragment>
|
||||
{highlights.map((h, idx) => (
|
||||
<rect
|
||||
onClick={onSelect}
|
||||
className='event'
|
||||
x={x}
|
||||
y={y - sectionHeight + (idx * sectionHeight)}
|
||||
style={{ ...styleProps, opacity: h ? 0.3 : 0.05 }}
|
||||
width={width}
|
||||
height={sectionHeight}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 <DatetimeDot
|
||||
onSelect={props.onSelect}
|
||||
@@ -22,7 +20,7 @@ function renderDot (event, styles, props) {
|
||||
}
|
||||
|
||||
function renderBar (event, styles, props) {
|
||||
const fillOpacity = GRAPH_NONLOCATED
|
||||
const fillOpacity = props.features.GRAPH_NONLOCATED
|
||||
? event.projectOffset >= 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 <React.Fragment>
|
||||
{projects.map(project => <Project
|
||||
|
||||
7
src/reducers/features.js
Normal file
7
src/reducers/features.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import initial from '../store/initial.js'
|
||||
|
||||
function features (featureState = initial.features, action) {
|
||||
return featureState
|
||||
}
|
||||
|
||||
export default features
|
||||
@@ -3,9 +3,11 @@ import { combineReducers } from 'redux'
|
||||
import domain from './domain.js'
|
||||
import app from './app.js'
|
||||
import ui from './ui.js'
|
||||
import features from './features.js'
|
||||
|
||||
export default combineReducers({
|
||||
app,
|
||||
domain,
|
||||
ui
|
||||
ui,
|
||||
features
|
||||
})
|
||||
|
||||
@@ -2,7 +2,8 @@ import Joi from 'joi'
|
||||
|
||||
const categorySchema = Joi.object().keys({
|
||||
category: Joi.string().required(),
|
||||
description: Joi.string()
|
||||
description: Joi.string(),
|
||||
group: Joi.string()
|
||||
})
|
||||
|
||||
export default categorySchema
|
||||
|
||||
@@ -417,6 +417,7 @@
|
||||
font-size: $normal;
|
||||
font-family: Helvetica, 'Georgia', 'serif';
|
||||
color: $midwhite;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createSelector } from 'reselect'
|
||||
import { compareTimestamp, insetSourceFrom, dateMin, dateMax } from '../common/utilities'
|
||||
import { isTimeRangedIn, shuffle } from './helpers'
|
||||
import { isTimeRangedIn } from './helpers'
|
||||
import { sizes } from '../common/global'
|
||||
const GRAPH_NONLOCATED = 'GRAPH_NONLOCATED' in process.env.features && process.env.features.GRAPH_NONLOCATED
|
||||
|
||||
// Input selectors
|
||||
export const getEvents = state => 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, []]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user