mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-11 21:08:36 +03:00
Filter render broken; moved narrative and narrativeState to app.filters and restructured appropriately with narrativeIdx selector
This commit is contained in:
@@ -18,7 +18,7 @@ The URLs for these endpoints, as well as other configurable settings in your tim
|
||||
| SITES_EXT | Endpoint for sites, concatenated with SERVER_ROOT | String | Yes |
|
||||
| MAP_ANCHOR | Geographic coordinates for original map anchor | Array of numbers | No |
|
||||
| MAPBOX_TOKEN | Access token for Mapbox satellite imagery | String | No |
|
||||
| features.USE_FILTERS | Enable / Disable filters | boolean | No |
|
||||
| features.USE_ASSOCIATIONS | Enable / Disable filters | boolean | No |
|
||||
| features.USE_SEARCH | Enable / Disable search | boolean | No |
|
||||
| features.USE_SITES | Enable / Disable sites | boolean | No |
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ export function fetchDomain () {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let sourcesPromise = Promise.resolve([])
|
||||
if (features.USE_SOURCES) {
|
||||
if (!SOURCES_URL) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import * as actions from '../actions'
|
||||
import * as selectors from '../selectors'
|
||||
|
||||
import MediaOverlay from './Overlay/Media'
|
||||
import LoadingOverlay from './Overlay/Loading'
|
||||
@@ -42,9 +43,9 @@ class Dashboard extends React.Component {
|
||||
this.props.actions.fetchDomain()
|
||||
.then(domain =>
|
||||
this.props.actions.updateDomain({
|
||||
domain,
|
||||
features: this.props.features
|
||||
}))
|
||||
domain,
|
||||
features: this.props.features
|
||||
}))
|
||||
}
|
||||
// NOTE: hack to get the timeline to always show. Not entirely sure why
|
||||
// this is necessary.
|
||||
@@ -134,7 +135,7 @@ class Dashboard extends React.Component {
|
||||
|
||||
setNarrativeFromFilters (withSteps) {
|
||||
const { app, domain } = this.props
|
||||
let activeFilters = app.filters.filters
|
||||
let activeFilters = app.associations.filters
|
||||
|
||||
if (activeFilters.length === 0) {
|
||||
alert('No filters selected, cant narrativise')
|
||||
@@ -182,8 +183,8 @@ class Dashboard extends React.Component {
|
||||
if (typeof idx !== 'number') {
|
||||
let e = idx[0] || idx
|
||||
|
||||
if (this.props.app.narrative) {
|
||||
const { steps } = this.props.app.narrative
|
||||
if (this.props.app.associations.narrative) {
|
||||
const { steps } = this.props.app.associations.narrative
|
||||
// choose the first event at a given location
|
||||
const locationEventId = e.id
|
||||
const narrativeIdxObj = steps.find(s => s.id === locationEventId)
|
||||
@@ -213,14 +214,14 @@ class Dashboard extends React.Component {
|
||||
if (narrative === null) {
|
||||
this.handleSelect(events[idx - 1], 0)
|
||||
} else {
|
||||
this.selectNarrativeStep(this.props.app.narrativeState.current - 1)
|
||||
this.selectNarrativeStep(this.props.narrativeIdx - 1)
|
||||
}
|
||||
}
|
||||
const next = idx => {
|
||||
if (narrative === null) {
|
||||
this.handleSelect(events[idx + 1], 0)
|
||||
} else {
|
||||
this.selectNarrativeStep(this.props.app.narrativeState.current + 1)
|
||||
this.selectNarrativeStep(this.props.narrativeIdx + 1)
|
||||
}
|
||||
}
|
||||
if (selected.length > 0) {
|
||||
@@ -266,7 +267,7 @@ class Dashboard extends React.Component {
|
||||
return (
|
||||
<div >
|
||||
<Toolbar
|
||||
isNarrative={!!app.narrative}
|
||||
isNarrative={!!app.associations.narrative}
|
||||
methods={{
|
||||
onTitle: actions.toggleCover,
|
||||
onSelectFilter: filter => actions.toggleFilter('filters', filter),
|
||||
@@ -279,13 +280,13 @@ class Dashboard extends React.Component {
|
||||
methods={{
|
||||
onSelectNarrative: this.setNarrative,
|
||||
getCategoryColor: this.getCategoryColor,
|
||||
onSelect: app.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 1)
|
||||
onSelect: app.associations.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 1)
|
||||
}}
|
||||
/>
|
||||
<Timeline
|
||||
onKeyDown={this.onKeyDown}
|
||||
methods={{
|
||||
onSelect: app.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 0),
|
||||
onSelect: app.associations.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 0),
|
||||
onUpdateTimerange: actions.updateTimeRange,
|
||||
getCategoryColor: this.getCategoryColor
|
||||
}}
|
||||
@@ -293,25 +294,25 @@ class Dashboard extends React.Component {
|
||||
<CardStack
|
||||
timelineDims={app.timeline.dimensions}
|
||||
onViewSource={this.handleViewSource}
|
||||
onSelect={app.narrative ? this.selectNarrativeStep : this.handleSelect}
|
||||
onSelect={app.associations.narrative ? this.selectNarrativeStep : this.handleSelect}
|
||||
onHighlight={this.handleHighlight}
|
||||
onToggleCardstack={() => actions.updateSelected([])}
|
||||
getNarrativeLinks={event => this.getNarrativeLinks(event)}
|
||||
getCategoryColor={this.getCategoryColor}
|
||||
/>
|
||||
<StateOptions
|
||||
showing={features.FILTERS_AS_NARRATIVES && !app.narrative && app.filters.filters.length > 0}
|
||||
showing={features.FILTERS_AS_NARRATIVES && !app.associations.narrative && app.associations.filters.length > 0}
|
||||
timelineDims={app.timeline.dimensions}
|
||||
onClickHandler={this.setNarrativeFromFilters}
|
||||
/>
|
||||
<NarrativeControls
|
||||
narrative={app.narrative ? {
|
||||
...app.narrative,
|
||||
current: app.narrativeState.current
|
||||
narrative={app.associations.narrative ? {
|
||||
...app.associations.narrative,
|
||||
current: this.props.narrativeIdx
|
||||
} : null}
|
||||
methods={{
|
||||
onNext: () => this.selectNarrativeStep(this.props.app.narrativeState.current + 1),
|
||||
onPrev: () => this.selectNarrativeStep(this.props.app.narrativeState.current - 1),
|
||||
onNext: () => this.selectNarrativeStep(this.props.narrativeIdx + 1),
|
||||
onPrev: () => this.selectNarrativeStep(this.props.narrativeIdx - 1),
|
||||
onSelectNarrative: this.setNarrative
|
||||
}}
|
||||
/>
|
||||
@@ -360,6 +361,9 @@ function mapDispatchToProps (dispatch) {
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => state,
|
||||
state => ({
|
||||
...state,
|
||||
narrativeIdx: selectors.selectNarrativeIdx(state)
|
||||
}),
|
||||
mapDispatchToProps
|
||||
)(Dashboard)
|
||||
|
||||
@@ -275,11 +275,11 @@ function mapStateToProps (state) {
|
||||
shapes: selectors.selectShapes(state)
|
||||
},
|
||||
app: {
|
||||
views: state.app.filters.views,
|
||||
views: state.app.associations.views,
|
||||
selected: selectors.selectSelected(state),
|
||||
highlighted: state.app.highlighted,
|
||||
map: state.app.map,
|
||||
narrative: state.app.narrative,
|
||||
narrative: state.app.associations.narrative,
|
||||
flags: {
|
||||
isShowingSites: state.app.flags.isShowingSites
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ class Timeline extends React.Component {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
dimensions: selectors.selectDimensions(state),
|
||||
isNarrative: !!state.app.narrative,
|
||||
isNarrative: !!state.app.associations.narrative,
|
||||
domain: {
|
||||
events: selectors.selectStackedEvents(state),
|
||||
projects: selectors.selectProjects(state),
|
||||
@@ -409,7 +409,7 @@ function mapStateToProps (state) {
|
||||
selected: state.app.selected,
|
||||
language: state.app.language,
|
||||
timeline: state.app.timeline,
|
||||
narrative: state.app.narrative
|
||||
narrative: state.app.associations.narrative
|
||||
},
|
||||
ui: {
|
||||
dom: state.ui.dom,
|
||||
|
||||
@@ -121,7 +121,7 @@ class Toolbar extends React.Component {
|
||||
<Tabs selectedIndex={this.state._selected}>
|
||||
{features.USE_NARRATIVES ? this.renderToolbarNarrativePanel() : null}
|
||||
{features.CATEGORIES_AS_FILTERS ? this.renderToolbarCategoriesPanel() : null}
|
||||
{features.USE_FILTERS ? this.renderToolbarFilterPanel() : null}
|
||||
{features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null}
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
@@ -163,7 +163,7 @@ class Toolbar extends React.Component {
|
||||
<div className='toolbar-tabs'>
|
||||
{features.USE_NARRATIVES ? this.renderToolbarTab(narrativesIdx, narrativesLabel, 'timeline') : null}
|
||||
{features.CATEGORIES_AS_FILTERS ? this.renderToolbarTab(categoriesIdx, categoriesLabel, 'widgets') : null}
|
||||
{features.USE_FILTERS ? this.renderToolbarTab(filtersIdx, filtersLabel, 'filter_list') : null}
|
||||
{features.USE_ASSOCIATIONS ? this.renderToolbarTab(filtersIdx, filtersLabel, 'filter_list') : null}
|
||||
</div>
|
||||
<BottomActions
|
||||
info={{
|
||||
@@ -203,8 +203,8 @@ function mapStateToProps (state) {
|
||||
language: state.app.language,
|
||||
activeFilters: selectors.getActiveFilters(state),
|
||||
activeCategories: selectors.getActiveCategories(state),
|
||||
viewFilters: state.app.filters.views,
|
||||
narrative: state.app.narrative,
|
||||
viewFilters: state.app.associations.views,
|
||||
narrative: state.app.associations.narrative,
|
||||
sitesShowing: state.app.flags.isShowingSites,
|
||||
infoShowing: state.app.flags.isInfopopup,
|
||||
features: selectors.getFeatures(state)
|
||||
|
||||
@@ -85,14 +85,17 @@ function updateNarrative (appState, action) {
|
||||
|
||||
return {
|
||||
...appState,
|
||||
narrative: action.narrative,
|
||||
narrativeState: {
|
||||
current: action.narrative ? 0 : null
|
||||
},
|
||||
filters: {
|
||||
associations: {
|
||||
...appState.filters,
|
||||
timerange: [minTime, maxTime],
|
||||
mapBounds: (action.narrative) ? [cornerBound0, cornerBound1] : null
|
||||
narrative: action.narrative
|
||||
},
|
||||
map: {
|
||||
...appState.map,
|
||||
bounds: (action.narrative) ? [cornerBound0, cornerBound1] : null
|
||||
},
|
||||
timeline: {
|
||||
...appState.timeline,
|
||||
range: [minTime, maxTime]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const associationsSchema = Joi.object().keys({
|
||||
id: Joi.string().allow('').required(),
|
||||
desc: Joi.string().allow(''),
|
||||
mode: Joi.string().allow('').required(),
|
||||
filter_paths: Joi.array(),
|
||||
filter_paths: Joi.array()
|
||||
})
|
||||
|
||||
export default associationsSchema
|
||||
export default associationsSchema
|
||||
|
||||
@@ -6,19 +6,18 @@ import { isTimeRangedIn } from './helpers'
|
||||
export const getEvents = state => state.domain.events
|
||||
export const getCategories = state => state.domain.categories
|
||||
export const getNarratives = state => state.domain.narratives
|
||||
export const getActiveNarrative = state => state.app.narrative
|
||||
export const getActiveStep = state => state.app.narrativeState.current
|
||||
export const getActiveNarrative = state => state.app.associations.narrative
|
||||
export const getSelected = state => state.app.selected
|
||||
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 getFilterTree = state => state.domain.filters
|
||||
export const getActiveFilters = state => state.app.filters.filters
|
||||
export const getActiveCategories = state => state.app.filters.categories
|
||||
export const getActiveFilters = state => state.app.associations.filters
|
||||
export const getActiveCategories = state => state.app.associations.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 selectNarrative = state => state.app.associations.narrative
|
||||
export const getFeatures = state => state.features
|
||||
export const getEventRadius = state => state.ui.eventRadius
|
||||
|
||||
@@ -113,11 +112,30 @@ export const selectNarratives = createSelector(
|
||||
return narrativesMeta.map(n => narratives[n.id]).filter(d => d)
|
||||
})
|
||||
|
||||
/** We iterate through narrative.steps and check the idx there against the selected array and we return the idx */
|
||||
export const selectNarrativeIdx = createSelector(
|
||||
[getSelected, getActiveNarrative],
|
||||
(selected, narrative) => {
|
||||
// Only one event selected in narrative mode
|
||||
if (narrative === null) return -1
|
||||
|
||||
const selectedEvent = selected[0]
|
||||
let selectedIdx
|
||||
|
||||
narrative.steps.forEach((step, idx) => {
|
||||
if (selectedEvent.id === step.id) {
|
||||
selectedIdx = idx
|
||||
}
|
||||
})
|
||||
return selectedIdx
|
||||
}
|
||||
)
|
||||
|
||||
/** Aggregate information about the narrative and the current step into
|
||||
* a single object. If narrative is null, the whole object is null.
|
||||
*/
|
||||
export const selectActiveNarrative = createSelector(
|
||||
[getActiveNarrative, getActiveStep],
|
||||
[getActiveNarrative, selectNarrativeIdx],
|
||||
(narrative, current) => narrative
|
||||
? { ...narrative, current }
|
||||
: null
|
||||
|
||||
@@ -34,12 +34,9 @@ const initial = {
|
||||
highlighted: null,
|
||||
selected: [],
|
||||
source: null,
|
||||
narrative: null,
|
||||
narrativeState: {
|
||||
current: null
|
||||
},
|
||||
filters: {
|
||||
associations: {
|
||||
filters: [],
|
||||
narrative: null,
|
||||
categories: [],
|
||||
views: {
|
||||
events: true,
|
||||
|
||||
Reference in New Issue
Block a user