mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-11 21:08:36 +03:00
Apply better naming and cleanup
This commit is contained in:
@@ -145,12 +145,12 @@ export function updateTagFilters(tag) {
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE'
|
||||
export function updateTimeRange(range) {
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
|
||||
export function updateTimeRange(timerange) {
|
||||
return {
|
||||
type: UPDATE_TIMERANGE,
|
||||
range
|
||||
}
|
||||
timerange
|
||||
};
|
||||
}
|
||||
|
||||
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
|
||||
|
||||
@@ -58,30 +58,6 @@ class Card extends React.Component {
|
||||
</div>);
|
||||
}
|
||||
|
||||
// NB: is this function for a future feature?
|
||||
renderIncidents() {
|
||||
const incident_type_lang = copy[this.props.language].cardstack.incident_type;
|
||||
const incidentTags = []; //this.props.event.tags.filter(tag => tag.type === 'incident_type');
|
||||
|
||||
return (<div className="event-card-section event-type">
|
||||
<h4>{incident_type_lang}</h4>
|
||||
{
|
||||
incidentTags.map((tag, idx) => {
|
||||
return (<span className={(
|
||||
tag.name === 'contradicción' || tag.name === 'declaración con sospecha de tortura')
|
||||
? ' flagged'
|
||||
: ''}>
|
||||
{tag.name}{
|
||||
(idx < incidentTags.length - 1)
|
||||
? ','
|
||||
: ''
|
||||
}
|
||||
</span>);
|
||||
})
|
||||
}
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderSummary() {
|
||||
const summary = copy[this.props.language].cardstack.description;
|
||||
const desc = this.props.event.description;
|
||||
@@ -162,15 +138,6 @@ class Card extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
return (<div className="card-collapsed">
|
||||
{this.renderWarning()}
|
||||
{this.renderCategory()}
|
||||
{this.renderTimestamp()}
|
||||
{this.renderSummary()}
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderCardLink(event, direction) {
|
||||
if (event !== null) {
|
||||
const timelabel = this.getTimeLabel();
|
||||
@@ -183,6 +150,7 @@ class Card extends React.Component {
|
||||
|
||||
renderNarrative() {
|
||||
const links = this.props.getNarrativeLinks(this.props.event);
|
||||
|
||||
if (links !== null) {
|
||||
return (<div className="event-card-section">
|
||||
<h4>Connected events</h4>
|
||||
@@ -192,6 +160,22 @@ class Card extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderSpinner() {
|
||||
return (<div className="spinner">
|
||||
<div className="double-bounce1"></div>
|
||||
<div className="double-bounce2"></div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
return (<div className="card-collapsed">
|
||||
{this.renderWarning()}
|
||||
{this.renderCategory()}
|
||||
{this.renderTimestamp()}
|
||||
{this.renderSummary()}
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
if (this.state.isFolded) {
|
||||
return (<div className="card-bottomhalf folded"></div>);
|
||||
@@ -200,31 +184,15 @@ class Card extends React.Component {
|
||||
{this.renderSpinner()}
|
||||
</div>);
|
||||
} else {
|
||||
if (!this.props.event.hasOwnProperty('receiver') && !this.props.event.hasOwnProperty('transmitter')) {
|
||||
return (<div className="card-bottomhalf">
|
||||
{this.renderLocation()}
|
||||
{this.renderTags()}
|
||||
{this.renderSource()}
|
||||
{this.renderNarrative()}
|
||||
</div>);
|
||||
} else {
|
||||
return (<div className="card-bottomhalf">
|
||||
{this.renderTags()}
|
||||
{this.renderSource()}
|
||||
{this.renderNarrative()}
|
||||
</div>);
|
||||
}
|
||||
return (<div className="card-bottomhalf">
|
||||
{this.renderLocation()}
|
||||
{this.renderTags()}
|
||||
{this.renderSource()}
|
||||
{this.renderNarrative()}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderSpinner() {
|
||||
return (<div className="spinner">
|
||||
<div className="double-bounce1"></div>
|
||||
<div className="double-bounce2"></div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderArrow() {
|
||||
let classes = (this.state.isFolded)
|
||||
? 'arrow-down folded'
|
||||
|
||||
@@ -38,11 +38,11 @@ class Dashboard extends React.Component {
|
||||
|
||||
handleSelect(selected) {
|
||||
if (selected) {
|
||||
// attacks are not susceptible to tag filters, so make sure this happens only when they are found
|
||||
// in the domain
|
||||
let eventsToSelect = selected.map(eventId => this.props.domain.events[eventId]);
|
||||
const parser = this.props.ui.tools.parser;
|
||||
|
||||
eventsToSelect = eventsToSelect.sort((a, b) => {
|
||||
return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
|
||||
return parser(a.timestamp) - parser(b.timestamp);
|
||||
});
|
||||
|
||||
if (eventsToSelect.every(event => (event))) {
|
||||
@@ -58,7 +58,7 @@ class Dashboard extends React.Component {
|
||||
});
|
||||
|
||||
eventsSelected = eventsSelected.sort((a, b) => {
|
||||
return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
|
||||
return parser(a.timestamp) - parser(b.timestamp);
|
||||
});
|
||||
|
||||
this.props.actions.updateSelected(eventsSelected);
|
||||
@@ -105,8 +105,8 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
|
||||
getCategoryLabel(category) {
|
||||
const label = this.props.domain.categories.find(t => t.category === category).category_label;
|
||||
return label;
|
||||
const categories = this.props.domain.categories;
|
||||
return categories.find(t => t.category === category).category_label;
|
||||
}
|
||||
|
||||
getNarrativeLinks(event) {
|
||||
@@ -176,7 +176,7 @@ class Dashboard extends React.Component {
|
||||
narratives={this.props.domain.narratives}
|
||||
categoryGroups={this.props.domain.categoryGroups}
|
||||
|
||||
range={this.props.app.filters.range}
|
||||
timerange={this.props.app.filters.timerange}
|
||||
selected={this.props.app.selected}
|
||||
language={this.props.app.language}
|
||||
|
||||
@@ -202,7 +202,7 @@ class Dashboard extends React.Component {
|
||||
toggle={() => this.handleToggle('TOGGLE_NOTIFICATIONS')}
|
||||
/>
|
||||
<LoadingOverlay
|
||||
ui={this.props.ui}
|
||||
ui={this.props.ui.flags.isFetchingDomain}
|
||||
language={this.props.app.language}
|
||||
/>
|
||||
</div>
|
||||
@@ -218,21 +218,23 @@ function mapStateToProps(state) {
|
||||
return Object.assign({}, state, {
|
||||
domain: Object.assign({}, state.domain, {
|
||||
|
||||
events: selectors.getFilteredEvents(state),
|
||||
locations: selectors.getFilteredLocations(state),
|
||||
categories: selectors.getFilteredCategories(state),
|
||||
categoryGroups: selectors.getCategoryGroups(state),
|
||||
sites: selectors.getSites(state),
|
||||
tags: selectors.getAllTags(state),
|
||||
narratives: selectors.getFilteredNarratives(state),
|
||||
// These items are affected by app selectionFilters
|
||||
events: selectors.selectEvents(state),
|
||||
locations: selectors.selectLocations(state),
|
||||
categories: selectors.selectCategories(state),
|
||||
categoryGroups: selectors.selectCategoryGroups(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
|
||||
notifications: state.domain.notifications,
|
||||
// These items are not affected by selectionFilters
|
||||
sites: selectors.getSites(state),
|
||||
tags: selectors.getTagTree(state),
|
||||
notifications: selectors.getNotifications(state)
|
||||
}),
|
||||
app: Object.assign({}, state.app, {
|
||||
error: state.app.error,
|
||||
filters: Object.assign({}, state.app.filters, {
|
||||
range: selectors.getRangeFilter(state),
|
||||
tags: selectors.getTagFilters(state)
|
||||
timerange: selectors.getTimeRange(state),
|
||||
tags: selectors.selectTagList(state)
|
||||
})
|
||||
}),
|
||||
ui: state.ui
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import copy from '../js/data/copy.json';
|
||||
|
||||
const LoadingOverlay = ({ ui, language }) => {
|
||||
const LoadingOverlay = ({ isFetchingDomain, language }) => {
|
||||
let classes = 'loading-overlay';
|
||||
classes += (!ui.flags.isFetchingDomain) ? ' hidden' : '';
|
||||
classes += (!isFetchingDomain) ? ' hidden' : '';
|
||||
|
||||
return (
|
||||
<div id="loading-overlay" className={classes}>
|
||||
|
||||
@@ -16,7 +16,7 @@ class Timeline extends React.Component {
|
||||
categoryGroups: this.props.categoryGroups
|
||||
}
|
||||
const app = {
|
||||
range: this.props.range,
|
||||
timerange: this.props.timerange,
|
||||
selected: this.props.selected,
|
||||
language: this.props.language,
|
||||
select: this.props.select,
|
||||
@@ -43,7 +43,7 @@ class Timeline extends React.Component {
|
||||
}
|
||||
|
||||
const app = {
|
||||
range: nextProps.range,
|
||||
timerange: nextProps.timerange,
|
||||
selected: nextProps.selected,
|
||||
language: nextProps.language,
|
||||
select: nextProps.select,
|
||||
@@ -75,8 +75,8 @@ class Timeline extends React.Component {
|
||||
const labels_title_lang = copy[this.props.language].timeline.labels_title;
|
||||
const info_lang = copy[this.props.language].timeline.info;
|
||||
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`;
|
||||
const date0 = this.props.tools.formatterWithYear(this.props.range[0]);
|
||||
const date1 = this.props.tools.formatterWithYear(this.props.range[1]);
|
||||
const date0 = this.props.tools.formatterWithYear(this.props.timerange[0]);
|
||||
const date1 = this.props.tools.formatterWithYear(this.props.timerange[1]);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="timeline-header">
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import '../scss/main.scss';
|
||||
import React from 'react';
|
||||
import Map from '../js/map/map.js';
|
||||
import { areEqual } from '../js/data/utilities.js';
|
||||
|
||||
class View2D extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const domain = {
|
||||
locations: this.props.locations,
|
||||
narratives: this.props.narratives,
|
||||
sites: this.props.sites,
|
||||
categoryGroups: this.props.categoryGroups
|
||||
}
|
||||
const app = {
|
||||
views: this.props.views,
|
||||
selected: this.props.selected,
|
||||
highlighted: this.props.highlighted,
|
||||
getCategoryGroup: this.props.getCategoryGroup,
|
||||
getCategoryGroupColor: this.props.getCategoryGroupColor,
|
||||
mapAnchor: this.props.mapAnchor
|
||||
}
|
||||
const ui = {
|
||||
style: this.props.uiStyle,
|
||||
dom: this.props.dom
|
||||
}
|
||||
|
||||
this.map = new Map(app, ui, this.props.select);
|
||||
this.map.update(domain, app);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const domain = {
|
||||
locations: nextProps.locations,
|
||||
narratives: nextProps.narratives,
|
||||
sites: nextProps.sites,
|
||||
categoryGroups: nextProps.categoryGroups
|
||||
}
|
||||
const app = {
|
||||
views: nextProps.views,
|
||||
selected: nextProps.selected,
|
||||
highlighted: nextProps.highlighted,
|
||||
getCategoryGroup: nextProps.getCategoryGroup,
|
||||
getCategoryGroupColor: nextProps.getCategoryGroupColor,
|
||||
mapAnchor: this.props.mapAnchor
|
||||
}
|
||||
|
||||
this.map.update(domain, app);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='map-wrapper'>
|
||||
<div id="map" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default View2D;
|
||||
@@ -1,36 +1,62 @@
|
||||
import '../scss/main.scss';
|
||||
import React from 'react';
|
||||
import View2D from './View2D.jsx';
|
||||
import Map from '../js/map/map.js';
|
||||
import { areEqual } from '../js/data/utilities.js';
|
||||
|
||||
class Viewport extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
if( this.props.isView2d ) {
|
||||
return (
|
||||
<View2D
|
||||
locations={this.props.locations}
|
||||
narratives={this.props.narratives}
|
||||
sites={this.props.sites}
|
||||
categoryGroups={this.props.categoryGroups}
|
||||
|
||||
views={this.props.views}
|
||||
selected={this.props.selected}
|
||||
highlighted={this.props.highlighted}
|
||||
mapAnchor={this.props.mapAnchor}
|
||||
|
||||
uiStyle={this.props.uiStyle}
|
||||
dom={this.props.dom}
|
||||
|
||||
select={this.props.select}
|
||||
highlight={this.props.highlight}
|
||||
getCategoryGroupColor={category => this.props.getCategoryGroupColor(category)}
|
||||
getCategoryGroup={category => this.props.getCategoryGroup(category)}
|
||||
/>
|
||||
);
|
||||
componentDidMount() {
|
||||
const domain = {
|
||||
locations: this.props.locations,
|
||||
narratives: this.props.narratives,
|
||||
sites: this.props.sites,
|
||||
categoryGroups: this.props.categoryGroups
|
||||
}
|
||||
const app = {
|
||||
views: this.props.views,
|
||||
selected: this.props.selected,
|
||||
highlighted: this.props.highlighted,
|
||||
getCategoryGroup: this.props.getCategoryGroup,
|
||||
getCategoryGroupColor: this.props.getCategoryGroupColor,
|
||||
mapAnchor: this.props.mapAnchor
|
||||
}
|
||||
const ui = {
|
||||
style: this.props.uiStyle,
|
||||
dom: this.props.dom
|
||||
}
|
||||
|
||||
this.map = new Map(app, ui, this.props.select);
|
||||
this.map.update(domain, app);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const domain = {
|
||||
locations: nextProps.locations,
|
||||
narratives: nextProps.narratives,
|
||||
sites: nextProps.sites,
|
||||
categoryGroups: nextProps.categoryGroups
|
||||
}
|
||||
const app = {
|
||||
views: nextProps.views,
|
||||
selected: nextProps.selected,
|
||||
highlighted: nextProps.highlighted,
|
||||
getCategoryGroup: nextProps.getCategoryGroup,
|
||||
getCategoryGroupColor: nextProps.getCategoryGroupColor,
|
||||
mapAnchor: this.props.mapAnchor
|
||||
}
|
||||
|
||||
this.map.update(domain, app);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='map-wrapper'>
|
||||
<div id="map" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function(app, ui) {
|
||||
let events = [];
|
||||
let categoryGroups = [];
|
||||
let selected = [];
|
||||
let range = app.range;
|
||||
let timerange = app.timerange;
|
||||
|
||||
const timeFilter = app.filter;
|
||||
const select = app.select;
|
||||
@@ -90,7 +90,7 @@ export default function(app, ui) {
|
||||
const scale = {};
|
||||
|
||||
scale.x = d3.scaleTime()
|
||||
.domain(range)
|
||||
.domain(timerange)
|
||||
.range([mg.l, WIDTH]);
|
||||
|
||||
const groupStep = (106 - 30) / categoryGroups.length;
|
||||
@@ -222,8 +222,8 @@ export default function(app, ui) {
|
||||
const dragNow = scale.x.invert(d3.event.x).getTime();
|
||||
const timeShift = (drag0 - dragNow) / 1000;
|
||||
|
||||
const newDomain0 = d3.timeSecond.offset(range[0], timeShift);
|
||||
const newDomainF = d3.timeSecond.offset(range[1], timeShift);
|
||||
const newDomain0 = d3.timeSecond.offset(timerange[0], timeShift);
|
||||
const newDomainF = d3.timeSecond.offset(timerange[1], timeShift);
|
||||
|
||||
scale.x.domain([newDomain0, newDomainF])
|
||||
render();
|
||||
@@ -417,7 +417,7 @@ export default function(app, ui) {
|
||||
* It automatically sets brush timeline to a domain set by the params
|
||||
*/
|
||||
function updateTimeRange() {
|
||||
scale.x.domain(range);
|
||||
scale.x.domain(timerange);
|
||||
axis.x0.scale(scale.x);
|
||||
axis.x1.scale(scale.x);
|
||||
}
|
||||
@@ -430,12 +430,12 @@ export default function(app, ui) {
|
||||
dom.axis.label0
|
||||
.attr('x', 5)
|
||||
.attr('y', 15)
|
||||
.text(formatterWithYear(range[0]));
|
||||
.text(formatterWithYear(timerange[0]));
|
||||
|
||||
dom.axis.label1
|
||||
.attr('x', WIDTH - 5)
|
||||
.attr('y', 15)
|
||||
.text(formatterWithYear(range[1]))
|
||||
.text(formatterWithYear(timerange[1]))
|
||||
.style('text-anchor', 'end');
|
||||
}
|
||||
|
||||
@@ -618,7 +618,7 @@ export default function(app, ui) {
|
||||
renderAxis();
|
||||
|
||||
events = domain.events;
|
||||
range = app.range;
|
||||
timerange = app.timerange;
|
||||
selected = app.selected.slice(0);
|
||||
updateTimeRange();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ function updateTagFilters(appState, action) {
|
||||
function updateTimeRange(appState, action) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
range: action.range
|
||||
timerange: action.timerange
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -58,7 +58,7 @@ function resetAllFilters(appState) { // XXX
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
tags: [],
|
||||
categories: [],
|
||||
range: [
|
||||
timerange: [
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-25T12:00:00"),
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-28T12:00:00")
|
||||
],
|
||||
|
||||
@@ -10,61 +10,67 @@ export const getSites = (state) => {
|
||||
if (process.env.features.USE_SITES) return state.domain.sites
|
||||
return []
|
||||
}
|
||||
export const getAllTags = state => state.domain.tags
|
||||
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 getCategoriesFilter = state => state.app.filters.categories
|
||||
export const getTagsFilter = state => state.app.filters.tags
|
||||
export const getRangeFilter = state => state.app.filters.range
|
||||
/**
|
||||
* Some handy helpers
|
||||
*/
|
||||
const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
|
||||
|
||||
// NB: should we stick with the default semantics and name these as selectors?
|
||||
// e.g. 'selectEvents', 'selectCoevents'.
|
||||
// Filter events
|
||||
function isTaggedIn (event, tagFilters) {
|
||||
/**
|
||||
* Given an event and all tags,
|
||||
* returns true/false if event has any tag that is active
|
||||
*/
|
||||
function isTaggedIn(event, tagFilters) {
|
||||
if (event.tags) {
|
||||
const tagsArray = event.tags.split(',')
|
||||
const isTagged = tagsArray.some((tag) => {
|
||||
return tagFilters.find((tagFilter) => {
|
||||
return (tagFilter.key === tag && tagFilter.active)
|
||||
})
|
||||
})
|
||||
return isTagged
|
||||
const tagsInEvent = event.tags.split(",");
|
||||
const isTagged = tagsInEvent.some((tag) => {
|
||||
return tagFilters.find(tF => (tF.key === tag && tF.active));
|
||||
});
|
||||
return isTagged;
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and a time range,
|
||||
* returns true/false if the event falls within timeRange
|
||||
*/
|
||||
function isTimeRangedIn(event, timeRange) {
|
||||
return (
|
||||
timeRange[0] < parseTimestamp(event.timestamp)
|
||||
&& parseTimestamp(event.timestamp) < timeRange[1]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function isNoTags(tagFilters) {
|
||||
return (
|
||||
tagFilters.length === 0
|
||||
|| !process.env.features.USE_TAGS
|
||||
|| tagFilters.every(t => !t.active)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Of all available events, selects those that fall within the time range,
|
||||
* and if TAGS are being used, select them if their tags are enabled
|
||||
*/
|
||||
export const getFilteredEvents = createSelector(
|
||||
[getEvents, getTagsFilter, getRangeFilter],
|
||||
(events, tagFilters, rangeFilter) => {
|
||||
return events.reduce((acc, value) => {
|
||||
const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active))
|
||||
export const selectEvents = createSelector(
|
||||
[getEvents, getTagsFilter, getTimeRange],
|
||||
(events, tagFilters, timeRange) => {
|
||||
|
||||
const isTagged = (noTags) || isTaggedIn(value, tagFilters)
|
||||
return events.reduce((acc, event) => {
|
||||
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters);
|
||||
const isTimeRanged = isTimeRangedIn(event, timeRange);
|
||||
|
||||
// TODO: put this datetime format as a constant
|
||||
const isRange = (rangeFilter[0] < d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp)) &&
|
||||
(d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp) < rangeFilter[1])
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (isRange && isTagged) {
|
||||
const event = Object.assign({}, value)
|
||||
acc[event.id] = event
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
)
|
||||
=======
|
||||
const isRange = (rangeFilter[0] < d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp)) &&
|
||||
(d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp) < rangeFilter[1]);
|
||||
|
||||
if (isRange && isTagged) {
|
||||
const event = Object.assign({}, value);
|
||||
acc[event.id] = event;
|
||||
if (isTimeRanged && isTagged) {
|
||||
const eventClone = Object.assign({}, event);
|
||||
acc[event.id] = eventClone;
|
||||
}
|
||||
|
||||
return acc;
|
||||
@@ -76,21 +82,20 @@ export const getFilteredEvents = createSelector(
|
||||
* Of all available events, selects those that fall within the time range,
|
||||
* and if TAGS are being used, select them if their tags are enabled
|
||||
*/
|
||||
const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
|
||||
export const getFilteredNarratives = createSelector(
|
||||
[getEvents, getTagsFilter, getRangeFilter],
|
||||
(events, tagFilters, rangeFilter) => {
|
||||
export const selectNarratives = createSelector(
|
||||
[getEvents, getTagsFilter, getTimeRange],
|
||||
(events, tagFilters, timeRange) => {
|
||||
|
||||
const narratives = {};
|
||||
events.forEach((evt) => {
|
||||
const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active));
|
||||
const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters);
|
||||
const isTimeRanged = isTimeRangedIn(evt, timeRange);
|
||||
const isInNarrative = evt.narrative;
|
||||
|
||||
const isTagged = (noTags) || isTaggedIn(evt, tagFilters);
|
||||
const isRange = (rangeFilter[0] < parseTimestamp(evt.timestamp)) &&
|
||||
(parseTimestamp(evt.timestamp) < rangeFilter[1]);
|
||||
|
||||
if (isRange && isTagged && evt.narrative) {
|
||||
if (!narratives[evt.narrative]) narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
|
||||
if (isTimeRanged && isTagged && isInNarrative) {
|
||||
if (!narratives[evt.narrative]) {
|
||||
narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
|
||||
}
|
||||
narratives[evt.narrative].steps.push(evt);
|
||||
narratives[evt.narrative].byId[evt.id] = { next: null, prev: null };
|
||||
}
|
||||
@@ -112,16 +117,18 @@ export const getFilteredNarratives = createSelector(
|
||||
* 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
|
||||
*/
|
||||
export const getFilteredLocations = createSelector(
|
||||
[getFilteredEvents],
|
||||
export const selectLocations = createSelector(
|
||||
[selectEvents],
|
||||
(events) => {
|
||||
const filteredLocations = {}
|
||||
|
||||
const selectedLocations = {};
|
||||
events.forEach(event => {
|
||||
const location = event.location
|
||||
if (filteredLocations[location]) {
|
||||
filteredLocations[location].events.push(event)
|
||||
const location = event.location;
|
||||
|
||||
if (selectedLocations[location]) {
|
||||
selectedLocations[location].events.push(event);
|
||||
} else {
|
||||
filteredLocations[location] = {
|
||||
selectedLocations[location] = {
|
||||
label: location,
|
||||
events: [event],
|
||||
latitude: event.latitude,
|
||||
@@ -130,51 +137,60 @@ export const getFilteredLocations = createSelector(
|
||||
}
|
||||
})
|
||||
|
||||
// Make locations an array are remove if any are undefined
|
||||
return Object.values(filteredLocations).filter(item => item)
|
||||
}
|
||||
)
|
||||
// Make locations an array are remove if any are undefined
|
||||
return Object.values(selectedLocations).filter(item => item);
|
||||
});
|
||||
|
||||
// Filter categories
|
||||
export const getFilteredCategories = createSelector(
|
||||
|
||||
/*
|
||||
* Select categories, return them as a list
|
||||
*/
|
||||
export const selectCategories = createSelector(
|
||||
[getCategories],
|
||||
(categories) => Object.values(categories))
|
||||
(categories) => {
|
||||
return Object.values(categories);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Return categories by group
|
||||
*/
|
||||
export const getCategoryGroups = createSelector(
|
||||
[getFilteredCategories],
|
||||
export const selectCategoryGroups = createSelector(
|
||||
[selectCategories],
|
||||
(categories) => {
|
||||
const groups = {}
|
||||
categories.forEach((t) => { if (t.group && !groups[t.group]) { groups[t.group] = t.group_label } })
|
||||
return Object.keys(groups).concat(['other'])
|
||||
const groups = {};
|
||||
categories.forEach((t) => {
|
||||
if (t.group && !groups[t.group]) {
|
||||
groups[t.group] = t.group_label;
|
||||
}
|
||||
});
|
||||
return Object.keys(groups).concat(['other']);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Given a tree of tags, return those tags as a list, where each node has been
|
||||
* aware of its depth, and given an 'active' flag
|
||||
* Given a tree of tags, return those tags as a list
|
||||
* Each node has been aware of its depth, and given an 'active' flag
|
||||
*/
|
||||
export const getTagFilters = createSelector(
|
||||
[getAllTags],
|
||||
export const selectTagList = createSelector(
|
||||
[getTagTree],
|
||||
(tags) => {
|
||||
const allTagFilters = []
|
||||
let depth = 0
|
||||
function traverseNode (node, depth) {
|
||||
node.active = (!node.hasOwnProperty('active')) ? false : node.active
|
||||
node.depth = depth
|
||||
if (node.active) allTagFilters.push(node)
|
||||
depth = depth + 1
|
||||
const tagList = [];
|
||||
let depth = 0;
|
||||
function traverseNode(node, 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)
|
||||
})
|
||||
traverseNode(childNode, depth + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tags && tags.key && tags.children) traverseNode(tags, depth)
|
||||
return allTagFilters
|
||||
if (tags.key && tags.children) traverseNode(tags, depth)
|
||||
return tagList;
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
|
||||
const initial = {
|
||||
domain: {
|
||||
events: [],
|
||||
narratives: [],
|
||||
narratives: [],
|
||||
locations: [],
|
||||
|
||||
categories: [],
|
||||
@@ -23,7 +23,7 @@ const initial = {
|
||||
selected: [],
|
||||
notifications: [],
|
||||
filters: {
|
||||
range: [
|
||||
timerange: [
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-22T12:00:00"),
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-27T12:00:00")
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user