mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 13:28:36 +03:00
factor step state of narrative from NarrativeCard comp to redux
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
import { selectActiveNarrative } from '../selectors'
|
||||
|
||||
class NarrativeCard extends React.Component {
|
||||
|
||||
@@ -59,12 +60,10 @@ class NarrativeCard extends React.Component {
|
||||
// no display if no narrative
|
||||
if (!this.props.narrative) return null
|
||||
|
||||
console.log(this.props.narrative)
|
||||
const { steps, current } = this.props.narrative
|
||||
|
||||
if (steps[current]) {
|
||||
const step = steps[current];
|
||||
console.log('here')
|
||||
|
||||
return (
|
||||
<div className='narrative-info'>
|
||||
@@ -89,7 +88,7 @@ class NarrativeCard extends React.Component {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
narrative: state.app.narrative
|
||||
narrative: selectActiveNarrative(state)
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps)(NarrativeCard);
|
||||
|
||||
@@ -35,31 +35,11 @@ function updateSelected(appState, action) {
|
||||
function updateNarrative(appState, action) {
|
||||
return {
|
||||
...appState,
|
||||
narrative: action.narrative
|
||||
narrative: action.narrative,
|
||||
narrativeState: {
|
||||
current: 0
|
||||
}
|
||||
}
|
||||
|
||||
// if (action.narrative === null) {
|
||||
// console.log(action.narrative)
|
||||
// return Object.assign({}, appState, {
|
||||
// narrative: action.narrative,
|
||||
// });
|
||||
// } else {
|
||||
// const dates = action.narrative.steps.map(n => parseDate(n.timestamp).getTime())
|
||||
// let minDate = Math.min(...dates);
|
||||
// let maxDate = Math.max(...dates);
|
||||
// // Add some margin to the datetime extent
|
||||
// minDate = minDate - ((maxDate - minDate) / 20);
|
||||
// maxDate = maxDate + ((maxDate - minDate) / 20);
|
||||
//
|
||||
// const output = Object.assign({}, appState, {
|
||||
// narrative: action.narrative,
|
||||
// filters: Object.assign({}, appState.filters, {
|
||||
// timerange: [new Date(minDate), new Date(maxDate)]
|
||||
// }),
|
||||
// });
|
||||
// console.log(output)
|
||||
// return output
|
||||
// }
|
||||
}
|
||||
|
||||
function updateTagFilters(appState, action) {
|
||||
|
||||
@@ -2,23 +2,25 @@ import { createSelector} from 'reselect'
|
||||
import { parseTimestamp, compareTimestamp } from '../js/utilities'
|
||||
|
||||
// Input selectors
|
||||
export const getEvents = state => state.domain.events;
|
||||
export const getLocations = state => state.domain.locations;
|
||||
export const getCategories = state => state.domain.categories;
|
||||
export const getNarratives = state => state.domain.narratives;
|
||||
export const getSelected = state => state.app.selected;
|
||||
export const getEvents = state => state.domain.events
|
||||
export const getLocations = state => state.domain.locations
|
||||
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 getSelected = state => state.app.selected
|
||||
export const getSites = (state) => {
|
||||
if (process.env.features.USE_SITES) return state.domain.sites;
|
||||
return [];
|
||||
if (process.env.features.USE_SITES) return state.domain.sites
|
||||
return []
|
||||
}
|
||||
export const getSources = state => {
|
||||
if (process.env.features.USE_SOURCES) return state.domain.sources;
|
||||
return [];
|
||||
if (process.env.features.USE_SOURCES) return state.domain.sources
|
||||
return []
|
||||
}
|
||||
export const getNotifications = state => state.domain.notifications;
|
||||
export const getTagTree = state => state.domain.tags;
|
||||
export const getTagsFilter = state => state.app.filters.tags;
|
||||
export const getTimeRange = state => state.app.filters.timerange;
|
||||
export const getNotifications = state => state.domain.notifications
|
||||
export const getTagTree = state => state.domain.tags
|
||||
export const getTagsFilter = state => state.app.filters.tags
|
||||
export const getTimeRange = state => state.app.filters.timerange
|
||||
|
||||
/**
|
||||
* Some handy helpers
|
||||
@@ -30,11 +32,11 @@ export const getTimeRange = state => state.app.filters.timerange;
|
||||
*/
|
||||
function isTaggedIn(event, tagFilters) {
|
||||
if (event.tags) {
|
||||
const tagsInEvent = event.tags.split(",");
|
||||
const tagsInEvent = event.tags.split(",")
|
||||
const isTagged = tagsInEvent.some((tag) => {
|
||||
return tagFilters.find(tF => (tF.key === tag && tF.active));
|
||||
});
|
||||
return isTagged;
|
||||
return tagFilters.find(tF => (tF.key === tag && tF.active))
|
||||
})
|
||||
return isTagged
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -48,7 +50,7 @@ function isNoTags(tagFilters) {
|
||||
tagFilters.length === 0
|
||||
|| !process.env.features.USE_TAGS
|
||||
|| tagFilters.every(t => !t.active)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +61,7 @@ function isTimeRangedIn(event, timeRange) {
|
||||
return (
|
||||
timeRange[0] < parseTimestamp(event.timestamp)
|
||||
&& parseTimestamp(event.timestamp) < timeRange[1]
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,17 +73,17 @@ export const selectEvents = createSelector(
|
||||
(events, tagFilters, timeRange) => {
|
||||
|
||||
return events.reduce((acc, event) => {
|
||||
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters);
|
||||
const isTimeRanged = isTimeRangedIn(event, timeRange);
|
||||
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters)
|
||||
const isTimeRanged = isTimeRangedIn(event, timeRange)
|
||||
|
||||
if (isTimeRanged && isTagged) {
|
||||
const eventClone = Object.assign({}, event);
|
||||
acc[event.id] = eventClone;
|
||||
const eventClone = Object.assign({}, event)
|
||||
acc[event.id] = eventClone
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
return acc
|
||||
}, [])
|
||||
})
|
||||
|
||||
/**
|
||||
* Of all available events, selects those that fall within the time range,
|
||||
@@ -91,14 +93,14 @@ export const selectNarratives = createSelector(
|
||||
[getEvents, getNarratives, getTagsFilter, getTimeRange],
|
||||
(events, narrativesMeta, tagFilters, timeRange) => {
|
||||
|
||||
const narratives = {};
|
||||
const narratives = {}
|
||||
const narrativeSkeleton = id => ({ id, steps: [] })
|
||||
|
||||
/* populate narratives dict with events */
|
||||
events.forEach(evt => {
|
||||
const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters);
|
||||
const isTimeRanged = isTimeRangedIn(evt, timeRange);
|
||||
const isInNarrative = evt.narratives.length > 0;
|
||||
const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters)
|
||||
const isTimeRanged = isTimeRangedIn(evt, timeRange)
|
||||
const isInNarrative = evt.narratives.length > 0
|
||||
|
||||
evt.narratives.forEach(narrative => {
|
||||
// initialise
|
||||
@@ -109,19 +111,19 @@ export const selectNarratives = createSelector(
|
||||
if (isInNarrative)
|
||||
narratives[narrative].steps.push(evt)
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
/* sort steps by time */
|
||||
Object.keys(narratives).forEach(key => {
|
||||
const steps = narratives[key].steps;
|
||||
const steps = narratives[key].steps
|
||||
|
||||
steps.sort(compareTimestamp);
|
||||
steps.sort(compareTimestamp)
|
||||
|
||||
// steps.forEach((step, i) => {
|
||||
// narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null;
|
||||
// narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null;
|
||||
// });
|
||||
// narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null
|
||||
// narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null
|
||||
// })
|
||||
|
||||
if (narrativesMeta.find(n => n.id === key)) {
|
||||
narratives[key] = {
|
||||
@@ -129,11 +131,20 @@ export const selectNarratives = createSelector(
|
||||
...narratives[key]
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return Object.values(narratives);
|
||||
});
|
||||
return Object.values(narratives)
|
||||
})
|
||||
|
||||
/** 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],
|
||||
(narrative, current) => !!narrative
|
||||
? { ...narrative, current }
|
||||
: null
|
||||
)
|
||||
/**
|
||||
* Of all the filtered events, group them by location and return a list of
|
||||
* locations with at least one event in it, based on the time range and tags
|
||||
@@ -142,12 +153,12 @@ export const selectLocations = createSelector(
|
||||
[selectEvents],
|
||||
(events) => {
|
||||
|
||||
const selectedLocations = {};
|
||||
const selectedLocations = {}
|
||||
events.forEach(event => {
|
||||
const location = event.location;
|
||||
const location = event.location
|
||||
|
||||
if (selectedLocations[location]) {
|
||||
selectedLocations[location].events.push(event);
|
||||
selectedLocations[location].events.push(event)
|
||||
} else {
|
||||
selectedLocations[location] = {
|
||||
label: location,
|
||||
@@ -158,9 +169,9 @@ export const selectLocations = createSelector(
|
||||
}
|
||||
})
|
||||
|
||||
return Object.values(selectedLocations);
|
||||
return Object.values(selectedLocations)
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
/**
|
||||
* Of all the sources, select those that are relevant to the selected events.
|
||||
@@ -176,7 +187,7 @@ export const selectSelected = createSelector(
|
||||
const srcs = selected
|
||||
.map(e => e.sources)
|
||||
.map(_sources => {
|
||||
if (!_sources) return [];
|
||||
if (!_sources) return []
|
||||
return _sources.map(id => (
|
||||
sources.hasOwnProperty(id) ? sources[id] : null
|
||||
))
|
||||
@@ -196,7 +207,7 @@ export const selectSelected = createSelector(
|
||||
export const selectCategories = createSelector(
|
||||
[getCategories],
|
||||
(categories) => categories
|
||||
);
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
@@ -206,23 +217,23 @@ export const selectCategories = createSelector(
|
||||
export const selectTagList = createSelector(
|
||||
[getTagTree],
|
||||
(tags) => {
|
||||
const tagList = [];
|
||||
let depth = 0;
|
||||
const tagList = []
|
||||
let depth = 0
|
||||
function traverseNode(node, depth) {
|
||||
node.active = (!node.hasOwnProperty('active')) ? false : node.active;
|
||||
node.depth = depth;
|
||||
node.active = (!node.hasOwnProperty('active')) ? false : node.active
|
||||
node.depth = depth
|
||||
|
||||
if (node.active) tagList.push(node)
|
||||
|
||||
if (Object.keys(node.children).length > 0) {
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
traverseNode(childNode, depth + 1);
|
||||
});
|
||||
traverseNode(childNode, depth + 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (tags && tags !== undefined) {
|
||||
if (tags.key && tags.children) traverseNode(tags, depth)
|
||||
}
|
||||
return tagList;
|
||||
return tagList
|
||||
}
|
||||
)
|
||||
|
||||
@@ -33,6 +33,9 @@ const initial = {
|
||||
selected: [],
|
||||
source: null,
|
||||
narrative: null,
|
||||
narrativeState: {
|
||||
current: null
|
||||
},
|
||||
filters: {
|
||||
timerange: [
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"),
|
||||
|
||||
Reference in New Issue
Block a user