mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 13:28:36 +03:00
WIP: simplify narratives selection
This commit is contained in:
@@ -25,9 +25,6 @@ class Dashboard extends React.Component {
|
||||
this.handleViewSource = this.handleViewSource.bind(this)
|
||||
this.handleHighlight = this.handleHighlight.bind(this);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.handleSelectNarrative = this.handleSelectNarrative.bind(this);
|
||||
this.handleTagFilter = this.handleTagFilter.bind(this);
|
||||
this.updateTimerange = this.updateTimerange.bind(this);
|
||||
this.getCategoryColor = this.getCategoryColor.bind(this);
|
||||
|
||||
this.eventsById = {}
|
||||
@@ -63,18 +60,6 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectNarrative(narrative) {
|
||||
this.props.actions.updateNarrative(narrative);
|
||||
}
|
||||
|
||||
handleTagFilter(tag) {
|
||||
this.props.actions.updateTagFilters(tag);
|
||||
}
|
||||
|
||||
updateTimerange(timeRange) {
|
||||
this.props.actions.updateTimeRange(timeRange);
|
||||
}
|
||||
|
||||
getCategoryColor(category='other') {
|
||||
return this.props.ui.style.categories[category] || this.props.ui.style.categories['other']
|
||||
}
|
||||
@@ -90,30 +75,32 @@ class Dashboard extends React.Component {
|
||||
<div>
|
||||
<Toolbar
|
||||
isNarrative={!!this.props.app.narrative}
|
||||
onFilter={this.handleTagFilter}
|
||||
onSelectNarrative={this.handleSelectNarrative}
|
||||
actions={this.props.actions}
|
||||
methods={{
|
||||
onFilter: this.props.actions.updateTagFilters,
|
||||
onSelectNarrative: this.props.actions.updateNarrative
|
||||
}}
|
||||
/>
|
||||
<Map
|
||||
mapId='map'
|
||||
methods={{
|
||||
onSelect: this.handleSelect,
|
||||
onSelectNarrative: this.handleSelectNarrative,
|
||||
onSelectNarrative: this.props.actions.updateNarrative,
|
||||
getCategoryColor: this.getCategoryColor,
|
||||
}}
|
||||
/>
|
||||
<Timeline
|
||||
methods={{
|
||||
onSelect: this.handleSelect,
|
||||
onUpdateTimerange: this.updateTimerange,
|
||||
onUpdateTimerange: this.props.actions.updateTimeRange,
|
||||
getCategoryColor: category => this.getCategoryColor(category)
|
||||
}}
|
||||
/>
|
||||
<NarrativeCard
|
||||
onSelect={this.handleSelect}
|
||||
onSelectNarrative={this.handleSelectNarrative}
|
||||
onSelectNarrative={this.props.actions.updateNarrative}
|
||||
/>
|
||||
<CardStack
|
||||
isNarrative={!!this.props.app.narrative}
|
||||
onViewSource={this.handleViewSource}
|
||||
onSelect={this.handleSelect}
|
||||
onHighlight={this.handleHighlight}
|
||||
|
||||
@@ -33,14 +33,15 @@ class MapEvents extends React.Component {
|
||||
})
|
||||
|
||||
if (this.props.narrative) {
|
||||
const { byId } = this.props.narrative
|
||||
const eventsInNarrative = events.filter(e => byId.hasOwnProperty(e.id))
|
||||
if (eventsInNarrative.length <= 0) {
|
||||
styleProps = {
|
||||
...styleProps,
|
||||
fillOpacity: 0.1
|
||||
}
|
||||
}
|
||||
// TODO: logic to display narratives in Map
|
||||
// const { byId } = this.props.narrative
|
||||
// const eventsInNarrative = events.filter(e => byId.hasOwnProperty(e.id))
|
||||
// if (eventsInNarrative.length <= 0) {
|
||||
// styleProps = {
|
||||
// ...styleProps,
|
||||
// fillOpacity: 0.1
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -74,13 +74,15 @@ class MapNarratives extends React.Component {
|
||||
}
|
||||
|
||||
renderNarrative(n) {
|
||||
const steps = n.steps.slice(0, n.steps.length - 1);
|
||||
|
||||
return (
|
||||
<g id={`narrative-${n.id.replace(/ /g,"_")}`} className="narrative">
|
||||
{steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))}
|
||||
</g>
|
||||
)
|
||||
// TODO: representation for narrative lines
|
||||
// const steps = n.steps.slice(0, n.steps.length - 1);
|
||||
//
|
||||
// return (
|
||||
// <g id={`narrative-${n.id.replace(/ /g,"_")}`} className="narrative">
|
||||
// {steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))}
|
||||
// </g>
|
||||
// )
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -56,11 +56,15 @@ class NarrativeCard extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
// 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'>
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 ToolbarBottomActions from './ToolbarBottomActions.jsx';
|
||||
import copy from '../js/data/copy.json';
|
||||
import { trimAndEllipse } from '../js/utilities.js';
|
||||
|
||||
@@ -46,8 +46,8 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
|
||||
goToNarrative(narrative) {
|
||||
this.selectTab(-1) // set all unselected
|
||||
this.props.onSelectNarrative(narrative);
|
||||
this.selectTab(-1) // set all unselected within this component
|
||||
this.props.methods.onSelectNarrative(narrative);
|
||||
}
|
||||
|
||||
renderToolbarNarrativePanel() {
|
||||
@@ -112,9 +112,7 @@ class Toolbar extends React.Component {
|
||||
{this.renderToolbarTab(0, 'Focus stories')}
|
||||
{this.renderToolbarTab(1, 'Explore freely')}
|
||||
</div>
|
||||
<ToolbarBottomActions
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
{/* <ToolbarBottomActions /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -161,9 +159,7 @@ class Toolbar extends React.Component {
|
||||
{this.renderToolbarTab(0, 'Narratives')}
|
||||
{(isTags) ? this.renderToolbarTab(1, 'Explore by tag') : ''}
|
||||
</div>
|
||||
<ToolbarBottomActions
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
{/* <ToolbarBottomActions /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,6 +73,13 @@ export function formatter(datetime) {
|
||||
return d3.timeFormat("%d %b, %H:%M")(datetime);
|
||||
}
|
||||
|
||||
export const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
|
||||
|
||||
export function compareTimestamp (a, b) {
|
||||
return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Debugging function: put in place of a mapStateToProps function to
|
||||
* view that source modal by default
|
||||
|
||||
@@ -33,11 +33,11 @@ function updateSelected(appState, action) {
|
||||
}
|
||||
|
||||
function updateNarrative(appState, action) {
|
||||
console.log(action.narrative)
|
||||
return {
|
||||
...appState,
|
||||
narrative: action.narrative
|
||||
}
|
||||
|
||||
// if (action.narrative === null) {
|
||||
// console.log(action.narrative)
|
||||
// return Object.assign({}, appState, {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSelector} from 'reselect'
|
||||
import { parseTimestamp, compareTimestamp } from '../js/utilities'
|
||||
|
||||
// Input selectors
|
||||
export const getEvents = state => state.domain.events;
|
||||
@@ -22,7 +23,6 @@ export const getTimeRange = state => state.app.filters.timerange;
|
||||
/**
|
||||
* Some handy helpers
|
||||
*/
|
||||
const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
|
||||
|
||||
/**
|
||||
* Given an event and all tags,
|
||||
@@ -89,40 +89,45 @@ export const selectEvents = createSelector(
|
||||
*/
|
||||
export const selectNarratives = createSelector(
|
||||
[getEvents, getNarratives, getTagsFilter, getTimeRange],
|
||||
(events, narrativeMetadata, tagFilters, timeRange) => {
|
||||
(events, narrativesMeta, tagFilters, timeRange) => {
|
||||
|
||||
const narratives = {};
|
||||
events.forEach((evt) => {
|
||||
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;
|
||||
|
||||
evt.narratives.map(narrative => {
|
||||
if (!narratives[narrative]) {
|
||||
narratives[narrative] = { id: narrative, steps: [], byId: {} };
|
||||
}
|
||||
evt.narratives.forEach(narrative => {
|
||||
// initialise
|
||||
if (!narratives[narrative])
|
||||
narratives[narrative] = narrativeSkeleton(narrative)
|
||||
|
||||
if (isInNarrative) {
|
||||
narratives[narrative].steps.push(evt);
|
||||
narratives[narrative].byId[evt.id] = { next: null, prev: null };
|
||||
}
|
||||
// add evt to steps
|
||||
if (isInNarrative)
|
||||
narratives[narrative].steps.push(evt)
|
||||
})
|
||||
});
|
||||
|
||||
Object.keys(narratives).forEach((key) => {
|
||||
|
||||
/* sort steps by time */
|
||||
Object.keys(narratives).forEach(key => {
|
||||
const steps = narratives[key].steps;
|
||||
|
||||
steps.sort((a, b) => {
|
||||
return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp));
|
||||
});
|
||||
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;
|
||||
});
|
||||
// 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;
|
||||
// });
|
||||
|
||||
if (narrativeMetadata.find(n => n.id === key)) {
|
||||
narratives[key] = Object.assign(narrativeMetadata.find(n => n.id === key), narratives[key]);
|
||||
if (narrativesMeta.find(n => n.id === key)) {
|
||||
narratives[key] = {
|
||||
...narrativesMeta.find(n => n.id === key),
|
||||
...narratives[key]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user