mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-13 05:48:36 +03:00
Merge pull request #21 from forensic-architecture/topic/with-example
Topic/with example
This commit is contained in:
@@ -6,7 +6,7 @@ cache:
|
||||
- node_modules
|
||||
before_script:
|
||||
- npm install -g yarn
|
||||
- cp config.example.js config.js
|
||||
- cp example.config.js config.js
|
||||
install:
|
||||
- yarn
|
||||
script:
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
module.exports = {
|
||||
title: 'EXAMPLE_TITLE',
|
||||
SERVER_ROOT: 'http://localhost:4040',
|
||||
EVENT_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev/rows',
|
||||
CATEGORY_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_category/rows',
|
||||
EVENT_DESC_ROOT: '/api/<ORIGIN_NAME>/MAP2D_dev/ids',
|
||||
TAG_TREE_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_tags/tree',
|
||||
SITES_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_sites/rows',
|
||||
MAP_ANCHOR: [27.5813121, -18.5161798],
|
||||
INCOMING_DATETIME_FORMAT: '%m/%d/%YT%H:%M',
|
||||
MAPBOX_TOKEN: 'SOME_MAPBOX_TOKEN',
|
||||
features: {
|
||||
USE_TAGS: false,
|
||||
USE_SEARCH: false,
|
||||
USE_SITES: false
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
module.exports = {
|
||||
title: 'EXAMPLE_TITLE',
|
||||
title: 'Example',
|
||||
SERVER_ROOT: 'http://localhost:4040',
|
||||
EVENT_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev/rows',
|
||||
CATEGORY_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_category/rows',
|
||||
EVENT_DESC_ROOT: '/api/<ORIGIN_NAME>/MAP2D_dev/ids',
|
||||
TAG_TREE_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_tags/tree',
|
||||
SITES_EXT: '/api/<ORIGIN_NAME>/MAP2D_dev_sites/rows',
|
||||
MAP_ANCHOR: [27.5813121, -18.5161798],
|
||||
EVENT_EXT: '/api/example/export_events/rows',
|
||||
CATEGORY_EXT: '/api/example/export_categories/rows',
|
||||
EVENT_DESC_ROOT: '/api/example/export_events/ids',
|
||||
TAG_TREE_EXT: '/api/example/export_tags/tree',
|
||||
SITES_EXT: '/api/example/export_sites/rows',
|
||||
MAP_ANCHOR: [31.356397, 34.784818],
|
||||
INCOMING_DATETIME_FORMAT: '%m/%d/%YT%H:%M',
|
||||
MAPBOX_TOKEN: 'SOME_MAPBOX_TOKEN',
|
||||
features: {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// TODO: relegate these URLs entirely to environment variables
|
||||
const EVENT_DATA_URL = `${process.env.SERVER_ROOT}${process.env.EVENT_EXT}`;
|
||||
const CATEGORY_URL = `${process.env.SERVER_ROOT}${process.env.CATEGORY_EXT}`;
|
||||
const TAG_TREE_URL = `${process.env.SERVER_ROOT}${process.env.TAG_TREE_EXT}`;
|
||||
const SITES_URL = `${process.env.SERVER_ROOT}${process.env.SITES_EXT}`;
|
||||
const EVENT_DATA_URL = `${process.env.SERVER_ROOT}${process.env.EVENT_EXT}`
|
||||
const CATEGORY_URL = `${process.env.SERVER_ROOT}${process.env.CATEGORY_EXT}`
|
||||
const TAG_TREE_URL = `${process.env.SERVER_ROOT}${process.env.TAG_TREE_EXT}`
|
||||
const SITES_URL = `${process.env.SERVER_ROOT}${process.env.SITES_EXT}`
|
||||
const eventUrlMap = (event) => `${process.env.SERVER_ROOT}${process.env.EVENT_DESC_ROOT}/${(event.id) ? event.id : event}`
|
||||
|
||||
/*
|
||||
* Create an error notification object
|
||||
* Types: ['error', 'warning', 'good', 'neural']
|
||||
*/
|
||||
function makeError(type, id, message) {
|
||||
function makeError (type, id, message) {
|
||||
return {
|
||||
type: 'error',
|
||||
id,
|
||||
@@ -17,65 +17,65 @@ function makeError(type, id, message) {
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchDomain() {
|
||||
let events = [];
|
||||
let categories = [];
|
||||
let sites = [];
|
||||
let notifications = [];
|
||||
let tags = {};
|
||||
export function fetchDomain () {
|
||||
let notifications = []
|
||||
|
||||
function makeError(domainType) {
|
||||
function handleError (domainType) {
|
||||
return () => {
|
||||
notifications.push({
|
||||
message: `Something went wrong fetching ${domainType}. Check the URL or try disabling them in the config file.`,
|
||||
type: 'error'
|
||||
});
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return dispatch => {
|
||||
dispatch(toggleFetchingDomain())
|
||||
const promises = []
|
||||
|
||||
const eventPromise = fetch(EVENT_DATA_URL)
|
||||
.then(response => response.json())
|
||||
.catch(handleError('events'))
|
||||
|
||||
const catPromise = fetch(CATEGORY_URL)
|
||||
.then(response => response.json())
|
||||
.catch(handleError('categories'))
|
||||
|
||||
let sitesPromise = Promise.resolve([])
|
||||
if (process.env.features.USE_SITES) {
|
||||
sitesPromise = fetch(SITES_URL)
|
||||
.then(response => response.json())
|
||||
.catch(handleError('sites'))
|
||||
}
|
||||
|
||||
return dispatch => {
|
||||
dispatch(toggleFetchingDomain());
|
||||
const promises = [];
|
||||
let tagsPromise
|
||||
if (process.env.features.USE_TAGS) {
|
||||
tagsPromise = fetch(TAG_TREE_URL)
|
||||
.then(response => response.json())
|
||||
.catch(handleError('tags'))
|
||||
}
|
||||
|
||||
const eventPromise = fetch(EVENT_DATA_URL)
|
||||
.then(response => response.json())
|
||||
.then(jsonEv => { events = jsonEv; })
|
||||
.catch(err => { makeError('events')});
|
||||
promises.push(eventPromise);
|
||||
|
||||
const catPromise = fetch(CATEGORY_URL)
|
||||
.then(response => response.json())
|
||||
.then(jsonCat => { categories = jsonCat; })
|
||||
.catch(err => { makeError('categories')});
|
||||
promises.push(catPromise);
|
||||
|
||||
if (process.env.features.USE_SITES) {
|
||||
const sitesPromise = fetch(SITES_URL)
|
||||
.then(response => response.json())
|
||||
.then(jsonSites => { sites = jsonSites; })
|
||||
.catch(err => { makeError('sites')});
|
||||
promises.push(sitesPromise);
|
||||
return Promise.all([ eventPromise, catPromise, sitesPromise, tagsPromise])
|
||||
.then(response => {
|
||||
dispatch(toggleFetchingDomain())
|
||||
const result = {
|
||||
events: response[0],
|
||||
categories: response[1],
|
||||
sites: response[2],
|
||||
tags: response[3],
|
||||
notifications
|
||||
}
|
||||
|
||||
if (process.env.features.USE_TAGS) {
|
||||
const tagTreePromise = fetch(TAG_TREE_URL)
|
||||
.then(response => response.json())
|
||||
.then(jsonTagTree => { tags = jsonTagTree; })
|
||||
.catch(err => { makeError('tags')});
|
||||
promises.push(tagTreePromise);
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(reponse => {
|
||||
dispatch(toggleFetchingDomain());
|
||||
return { events, categories, sites, tags, notifications };
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(fetchError(err.message))
|
||||
dispatch(toggleFetchingDomain());
|
||||
})
|
||||
};
|
||||
return result
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(fetchError(err.message))
|
||||
dispatch(toggleFetchingDomain())
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export const FETCH_ERROR = 'FETCH_ERROR';
|
||||
export const FETCH_ERROR = 'FETCH_ERROR'
|
||||
export function fetchError(message) {
|
||||
return {
|
||||
type: FETCH_ERROR,
|
||||
@@ -83,113 +83,114 @@ export function fetchError(message) {
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_DOMAIN = 'UPDATE_DOMAIN';
|
||||
export const UPDATE_DOMAIN = 'UPDATE_DOMAIN'
|
||||
export function updateDomain(domain) {
|
||||
return {
|
||||
type: UPDATE_DOMAIN,
|
||||
domain: {
|
||||
events: domain.events,
|
||||
categories: domain.categories,
|
||||
tags: domain.tags,
|
||||
sites: domain.sites,
|
||||
notifications: domain.notifications
|
||||
}
|
||||
};
|
||||
return {
|
||||
type: UPDATE_DOMAIN,
|
||||
domain: {
|
||||
events: domain.events,
|
||||
categories: domain.categories,
|
||||
tags: domain.tags,
|
||||
sites: domain.sites,
|
||||
notifications: domain.notifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchEvents(events) {
|
||||
return dispatch => {
|
||||
dispatch(toggleFetchingEvents());
|
||||
const urls = events.map(eventUrlMap);
|
||||
return Promise.all(urls.map(url => fetch(url)
|
||||
.then(response => response.json())
|
||||
)
|
||||
)
|
||||
.then(json => {
|
||||
dispatch(toggleFetchingEvents());
|
||||
return json;
|
||||
});
|
||||
};
|
||||
export function fetchEvents (events) {
|
||||
return dispatch => {
|
||||
dispatch(toggleFetchingEvents())
|
||||
const urls = events.map(eventUrlMap)
|
||||
return Promise.all(
|
||||
urls.map(url => fetch(url)
|
||||
.then(response => response.json())
|
||||
)
|
||||
)
|
||||
.then(json => {
|
||||
dispatch(toggleFetchingEvents())
|
||||
return json
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_HIGHLIGHTED = 'UPDATE_HIGHLIGHTED';
|
||||
export const UPDATE_HIGHLIGHTED = 'UPDATE_HIGHLIGHTED'
|
||||
export function updateHighlighted(highlighted) {
|
||||
return {
|
||||
type: UPDATE_HIGHLIGHTED,
|
||||
highlighted: highlighted
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_SELECTED = 'UPDATE_SELECTED';
|
||||
export const UPDATE_SELECTED = 'UPDATE_SELECTED'
|
||||
export function updateSelected(selected) {
|
||||
return {
|
||||
type: UPDATE_SELECTED,
|
||||
selected: selected
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_DISTRICT = 'UPDATE_DISTRICT';
|
||||
export const UPDATE_DISTRICT = 'UPDATE_DISTRICT'
|
||||
export function updateDistrict(district) {
|
||||
return {
|
||||
type: UPDATE_DISTRICT,
|
||||
district
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_TAGFILTERS = 'UPDATE_TIMEFILTERS';
|
||||
export const UPDATE_TAGFILTERS = 'UPDATE_TIMEFILTERS'
|
||||
export function updateTagFilters(tag) {
|
||||
return {
|
||||
type: UPDATE_TAGFILTERS,
|
||||
tag
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE'
|
||||
export function updateTimeRange(range) {
|
||||
return {
|
||||
type: UPDATE_TIMERANGE,
|
||||
range
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS';
|
||||
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
|
||||
export function resetAllFilters() {
|
||||
return {
|
||||
type: RESET_ALLFILTERS
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
export const TOGGLE_FETCHING_DOMAIN = 'TOGGLE_FETCHING_DOMAIN';
|
||||
export const TOGGLE_FETCHING_DOMAIN = 'TOGGLE_FETCHING_DOMAIN'
|
||||
export function toggleFetchingDomain() {
|
||||
return {
|
||||
type: TOGGLE_FETCHING_DOMAIN
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_FETCHING_EVENTS = 'TOGGLE_FETCHING_EVENTS';
|
||||
export const TOGGLE_FETCHING_EVENTS = 'TOGGLE_FETCHING_EVENTS'
|
||||
export function toggleFetchingEvents() {
|
||||
return {
|
||||
type: TOGGLE_FETCHING_EVENTS
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_VIEW = 'TOGGLE_VIEW';
|
||||
export const TOGGLE_VIEW = 'TOGGLE_VIEW'
|
||||
export function toggleView() {
|
||||
return {
|
||||
type: TOGGLE_VIEW
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_TIMELINE = 'TOGGLE_TIMELINE';
|
||||
export const TOGGLE_TIMELINE = 'TOGGLE_TIMELINE'
|
||||
export function toggleTimeline() {
|
||||
return {
|
||||
type: TOGGLE_TIMELINE
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE';
|
||||
export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE'
|
||||
export function toggleLanguage(language) {
|
||||
return {
|
||||
type: TOGGLE_LANGUAGE,
|
||||
@@ -197,46 +198,46 @@ export function toggleLanguage(language) {
|
||||
}
|
||||
}
|
||||
|
||||
export const OPEN_TOOLBAR = 'OPEN_TOOLBAR';
|
||||
export const OPEN_TOOLBAR = 'OPEN_TOOLBAR'
|
||||
export function openToolbar(toolbarTab = 0) {
|
||||
return {
|
||||
type: OPEN_TOOLBAR,
|
||||
toolbarTab: toolbarTab,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR';
|
||||
export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR'
|
||||
export function closeToolbar() {
|
||||
return {
|
||||
type: CLOSE_TOOLBAR
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const OPEN_CABINET = 'OPEN_CABINET';
|
||||
export const OPEN_CABINET = 'OPEN_CABINET'
|
||||
export function openCabinet(tabNum) {
|
||||
return {
|
||||
type: OPEN_CABINET,
|
||||
tabNum: tabNum,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const CLOSE_CABINET = 'CLOSE_CABINET';
|
||||
export const CLOSE_CABINET = 'CLOSE_CABINET'
|
||||
export function closeCabinet() {
|
||||
return {
|
||||
type: CLOSE_CABINET
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP';
|
||||
export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP'
|
||||
export function toggleInfoPopup() {
|
||||
return {
|
||||
type: TOGGLE_INFOPOPUP
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_NOTIFICATIONS = 'TOGGLE_NOTIFICATIONS';
|
||||
export const TOGGLE_NOTIFICATIONS = 'TOGGLE_NOTIFICATIONS'
|
||||
export function toggleNotifications() {
|
||||
return {
|
||||
type: TOGGLE_NOTIFICATIONS
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class Dashboard extends React.Component {
|
||||
componentDidMount() {
|
||||
if (!this.props.app.isMobile) {
|
||||
this.props.actions.fetchDomain()
|
||||
.then((domain) => this.props.actions.updateDomain(domain));
|
||||
.then((domain) => this.props.actions.updateDomain(domain))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,16 +59,24 @@ export default function(newApp, ui, select) {
|
||||
.setView(center, zoom)
|
||||
.setMinZoom(10)
|
||||
.setMaxZoom(18)
|
||||
.setMaxBounds(maxBoundaries);
|
||||
.setMaxBounds(maxBoundaries)
|
||||
|
||||
// NB: configure tile endpoint
|
||||
let s;
|
||||
if (process.env.MAPBOX_TOKEN) {
|
||||
let s
|
||||
if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== 'your_token') {
|
||||
s = L.tileLayer(
|
||||
`http://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
);
|
||||
} else {
|
||||
s = L.tileLayer(`${process.env.SERVER_ROOT}/mapbox/{z}/{x}/{y}`);
|
||||
// eslint-disable-next-line
|
||||
alert(`No mapbox token specified in config.
|
||||
Timemap does not currently support any other tiling layer,
|
||||
so you will need to sign up for one at:
|
||||
|
||||
https://www.mapbox.com/
|
||||
|
||||
Stop and start the development process in terminal after you have added your token to config.js`)
|
||||
return
|
||||
}
|
||||
s = s.addTo(map);
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import initial from '../store/initial.js';
|
||||
import initial from '../store/initial.js'
|
||||
|
||||
import {
|
||||
UPDATE_DOMAIN,
|
||||
} from '../actions';
|
||||
import { UPDATE_DOMAIN } from '../actions'
|
||||
import { parseDateTimes } from './utils/helpers.js'
|
||||
import { validate } from './utils/validators.js'
|
||||
|
||||
import { parseDateTimes } from './utils/helpers.js';
|
||||
import { validate } from './utils/validators.js';
|
||||
function updateDomain (domainState, action) {
|
||||
action.domain.events = parseDateTimes(action.domain.events)
|
||||
|
||||
function updateDomain(domainState, action) {
|
||||
action.domain.events = parseDateTimes(action.domain.events);
|
||||
|
||||
return Object.assign({}, domainState, validate(action.domain));
|
||||
return Object.assign({}, domainState, validate(action.domain))
|
||||
}
|
||||
|
||||
function domain(domainState = initial.domain, action) {
|
||||
function domain (domainState = initial.domain, action) {
|
||||
switch (action.type) {
|
||||
case UPDATE_DOMAIN:
|
||||
return updateDomain(domainState, action);
|
||||
return updateDomain(domainState, action)
|
||||
default:
|
||||
return domainState;
|
||||
return domainState
|
||||
}
|
||||
}
|
||||
|
||||
export default domain;
|
||||
export default domain
|
||||
|
||||
@@ -26,23 +26,26 @@ const isDuplicate = (node, set) => { return (set.has(node.key)); };
|
||||
/*
|
||||
* Traverse a tag tree and check its duplicates
|
||||
*/
|
||||
function validateTree(node, parent, set, duplicates) {
|
||||
function validateTree (node, parent, set, duplicates) {
|
||||
if (!Array.isArray(node) || !node.length) {
|
||||
return
|
||||
}
|
||||
// If it's a leaf, check that it's not duplicate
|
||||
if (isLeaf(node)) {
|
||||
if (isDuplicate(node, set)) {
|
||||
duplicates.push({
|
||||
id: node.key,
|
||||
error: makeError('Tags', node.key, 'tag was found more than once in hierarchy. Ignoring duplicate.')
|
||||
});
|
||||
delete parent.children[node.key];
|
||||
})
|
||||
delete parent.children[node.key]
|
||||
} else {
|
||||
set.add(node.key);
|
||||
set.add(node.key)
|
||||
}
|
||||
} else {
|
||||
// If it's not a leaf, simply keep going
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
validateTree(childNode, node, set, duplicates);
|
||||
});
|
||||
validateTree(childNode, node, set, duplicates)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,60 +3,60 @@ import {
|
||||
} from 'reselect'
|
||||
|
||||
// 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 getEvents = state => state.domain.events
|
||||
export const getLocations = state => state.domain.locations
|
||||
export const getCategories = state => state.domain.categories
|
||||
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 getAllTags = state => state.domain.tags;
|
||||
export const getAllTags = state => state.domain.tags
|
||||
|
||||
export const getCategoriesFilter = state => state.app.filters.categories;
|
||||
export const getTagsFilter = state => state.app.filters.tags;
|
||||
export const getRangeFilter = state => state.app.filters.range;
|
||||
export const getCategoriesFilter = state => state.app.filters.categories
|
||||
export const getTagsFilter = state => state.app.filters.tags
|
||||
export const getRangeFilter = state => state.app.filters.range
|
||||
|
||||
// NB: should we stick with the default semantics and name these as selectors?
|
||||
// e.g. 'selectEvents', 'selectCoevents'.
|
||||
// Filter events
|
||||
function isTaggedIn(event, tagFilters) {
|
||||
function isTaggedIn (event, tagFilters) {
|
||||
if (event.tags) {
|
||||
const tagsArray = event.tags.split(",");
|
||||
const tagsArray = event.tags.split(',')
|
||||
const isTagged = tagsArray.some((tag) => {
|
||||
return tagFilters.find((tagFilter) => {
|
||||
return (tagFilter.key === tag && tagFilter.active);
|
||||
return (tagFilter.key === tag && tagFilter.active)
|
||||
})
|
||||
});
|
||||
return isTagged;
|
||||
})
|
||||
return isTagged
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
[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))
|
||||
|
||||
return events.reduce((acc, value) => {
|
||||
const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active));
|
||||
const isTagged = (noTags) || isTaggedIn(value, tagFilters)
|
||||
|
||||
const isTagged = (noTags) || isTaggedIn(value, tagFilters);
|
||||
// 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])
|
||||
|
||||
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;
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
});
|
||||
if (isRange && isTagged) {
|
||||
const event = Object.assign({}, value)
|
||||
acc[event.id] = event
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Of all the filtered events, group them by location and return a list of
|
||||
@@ -65,12 +65,11 @@ export const getFilteredEvents = createSelector(
|
||||
export const getFilteredLocations = createSelector(
|
||||
[getFilteredEvents],
|
||||
(events) => {
|
||||
|
||||
const filteredLocations = {};
|
||||
const filteredLocations = {}
|
||||
events.forEach(event => {
|
||||
const location = event.location;
|
||||
const location = event.location
|
||||
if (filteredLocations[location]) {
|
||||
filteredLocations[location].events.push(event);
|
||||
filteredLocations[location].events.push(event)
|
||||
} else {
|
||||
filteredLocations[location] = {
|
||||
label: location,
|
||||
@@ -81,17 +80,15 @@ 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(filteredLocations).filter(item => item)
|
||||
}
|
||||
)
|
||||
|
||||
// Filter categories
|
||||
export const getFilteredCategories = createSelector(
|
||||
[getCategories],
|
||||
(categories) => {
|
||||
|
||||
return Object.values(categories);
|
||||
});
|
||||
(categories) => Object.values(categories))
|
||||
|
||||
/**
|
||||
* Return categories by group
|
||||
@@ -99,13 +96,12 @@ export const getFilteredCategories = createSelector(
|
||||
export const getCategoryGroups = createSelector(
|
||||
[getFilteredCategories],
|
||||
(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
|
||||
@@ -113,22 +109,22 @@ export const getCategoryGroups = createSelector(
|
||||
export const getTagFilters = createSelector(
|
||||
[getAllTags],
|
||||
(tags) => {
|
||||
const allTagFilters = [];
|
||||
let depth = 0;
|
||||
function traverseNode(node, depth) {
|
||||
node.active = (!node.hasOwnProperty('active')) ? false : node.active;
|
||||
node.depth = depth;
|
||||
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;
|
||||
depth = depth + 1
|
||||
|
||||
if (Object.keys(node.children).length > 0) {
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
traverseNode(childNode, depth);
|
||||
});
|
||||
traverseNode(childNode, depth)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (tags.key && tags.children) traverseNode(tags, depth)
|
||||
return allTagFilters;
|
||||
if (tags && tags.key && tags.children) traverseNode(tags, depth)
|
||||
return allTagFilters
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user