mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 21:38:35 +03:00
Clean master commit
This commit is contained in:
88
src/reducers/app.js
Normal file
88
src/reducers/app.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import initial from '../store/initial.js';
|
||||
|
||||
import {
|
||||
UPDATE_HIGHLIGHTED,
|
||||
UPDATE_SELECTED,
|
||||
UPDATE_FILTERS,
|
||||
UPDATE_TIMERANGE,
|
||||
RESET_ALLFILTERS,
|
||||
TOGGLE_LANGUAGE,
|
||||
FETCH_ERROR,
|
||||
} from '../actions';
|
||||
|
||||
function updateHighlighted(appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
highlighted: action.highlighted
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelected(appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
selected: action.selected
|
||||
});
|
||||
}
|
||||
|
||||
function updateFilters(appState, action) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, action.filters)
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimeRange(appState, action) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, action.range),
|
||||
});
|
||||
}
|
||||
|
||||
function resetAllFilters(appState) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
tags: [],
|
||||
categories: [],
|
||||
range: [
|
||||
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")
|
||||
],
|
||||
}),
|
||||
selected: [],
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLanguage(appState, action) {
|
||||
let otherLanguage = (appState.language === 'es-MX') ? 'en-US' : 'es-MX';
|
||||
return Object.assign({}, appState, {
|
||||
language: action.language || otherLanguage
|
||||
});
|
||||
}
|
||||
|
||||
function fetchError(state, action) {
|
||||
return {
|
||||
...state,
|
||||
error: action.message,
|
||||
notifications: [{ type: 'error', message: action.message }]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function app(appState = initial.app, action) {
|
||||
switch (action.type) {
|
||||
case UPDATE_HIGHLIGHTED:
|
||||
return updateHighlighted(appState, action);
|
||||
case UPDATE_SELECTED:
|
||||
return updateSelected(appState, action);
|
||||
case UPDATE_FILTERS:
|
||||
return updateFilters(appState, action);
|
||||
case UPDATE_TIMERANGE:
|
||||
return updateTimeRange(appState, action);
|
||||
case RESET_ALLFILTERS:
|
||||
return resetAllFilters(appState, action);
|
||||
case TOGGLE_LANGUAGE:
|
||||
return toggleLanguage(appState, action);
|
||||
case FETCH_ERROR:
|
||||
return fetchError(appState, action);
|
||||
default:
|
||||
return appState;
|
||||
}
|
||||
}
|
||||
|
||||
export default app;
|
||||
25
src/reducers/domain.js
Normal file
25
src/reducers/domain.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import initial from '../store/initial.js';
|
||||
|
||||
import {
|
||||
UPDATE_DOMAIN,
|
||||
} from '../actions';
|
||||
|
||||
import { parseDateTimes } from './utils/helpers.js';
|
||||
import { validate } from './utils/validators.js';
|
||||
|
||||
function updateDomain(domainState, action) {
|
||||
action.domain.events = parseDateTimes(action.domain.events);
|
||||
|
||||
return Object.assign({}, domainState, validate(action.domain));
|
||||
}
|
||||
|
||||
function domain(domainState = initial.domain, action) {
|
||||
switch (action.type) {
|
||||
case UPDATE_DOMAIN:
|
||||
return updateDomain(domainState, action);
|
||||
default:
|
||||
return domainState;
|
||||
}
|
||||
}
|
||||
|
||||
export default domain;
|
||||
13
src/reducers/index.js
Normal file
13
src/reducers/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
combineReducers
|
||||
} from 'redux'
|
||||
|
||||
import domain from './domain.js'
|
||||
import app from './app.js'
|
||||
import ui from './ui.js'
|
||||
|
||||
export default combineReducers({
|
||||
app,
|
||||
domain,
|
||||
ui
|
||||
});;
|
||||
12
src/reducers/schema/categorySchema.js
Normal file
12
src/reducers/schema/categorySchema.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
const categorySchema = Joi.object().keys({
|
||||
category: Joi.string().required(),
|
||||
category_label: Joi.string().allow('').required(),
|
||||
group: Joi.string(),
|
||||
group_label: Joi.string(),
|
||||
});
|
||||
|
||||
const optionalSchema = categorySchema.optionalKeys('group', 'group_label');
|
||||
|
||||
export default categorySchema;
|
||||
20
src/reducers/schema/eventSchema.js
Normal file
20
src/reducers/schema/eventSchema.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
const eventSchema = Joi.object().keys({
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
date: Joi.string().required(),
|
||||
time: Joi.string().required(),
|
||||
time_precision: Joi.string().allow(''),
|
||||
location: Joi.string().allow('').required(),
|
||||
latitude: Joi.string().required(),
|
||||
longitude: Joi.string().required(),
|
||||
type: Joi.string().allow(''),
|
||||
category: Joi.string().required(),
|
||||
source: Joi.string().allow(''),
|
||||
tags: Joi.string().allow(''),
|
||||
comments: Joi.string().allow(''),
|
||||
timestamp: Joi.string().required(),
|
||||
});
|
||||
|
||||
export default eventSchema;
|
||||
11
src/reducers/schema/siteSchema.js
Normal file
11
src/reducers/schema/siteSchema.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
const siteSchema = Joi.object().keys({
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
site: Joi.string().required(),
|
||||
latitude: Joi.string().required(),
|
||||
longitude: Joi.string().required()
|
||||
});
|
||||
|
||||
export default siteSchema;
|
||||
104
src/reducers/ui.js
Normal file
104
src/reducers/ui.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import initial from '../store/initial.js';
|
||||
|
||||
import {
|
||||
TOGGLE_FETCHING_DOMAIN,
|
||||
TOGGLE_FETCHING_EVENTS,
|
||||
TOGGLE_VIEW,
|
||||
TOGGLE_TIMELINE,
|
||||
OPEN_CABINET,
|
||||
CLOSE_CABINET,
|
||||
TOGGLE_INFOPOPUP,
|
||||
TOGGLE_NOTIFICATIONS
|
||||
} from '../actions'
|
||||
|
||||
function toggleFetchingDomain(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isFetchingDomain: !uiState.flags.isFetchingDomain
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function toggleFetchingEvents(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isFetchingEvents: !uiState.flags.isFetchingEvents
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function toggleView(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isView2d: !uiState.flags.isView2d
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTimeline(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isTimeline: !uiState.flags.isTimeline
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function closeCabinet(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isCabinet: false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function openCabinet(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isCabinet: true
|
||||
}),
|
||||
components: Object.assign({}, uiState.components, {
|
||||
cabinetFileTab: action.tabNum,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function toggleInfoPopup(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isInfopopup: !uiState.flags.isInfopopup
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function toggleNotifications(uiState, action) {
|
||||
return Object.assign({}, uiState, {
|
||||
flags: Object.assign({}, uiState.flags, {
|
||||
isNotification: !uiState.flags.isNotification
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function ui(uiState = initial.ui, action) {
|
||||
switch (action.type) {
|
||||
case TOGGLE_FETCHING_DOMAIN:
|
||||
return toggleFetchingDomain(uiState, action);
|
||||
case TOGGLE_FETCHING_EVENTS:
|
||||
return toggleFetchingEvents(uiState, action);
|
||||
case TOGGLE_VIEW:
|
||||
return toggleView(uiState, action);
|
||||
case TOGGLE_TIMELINE:
|
||||
return toggleTimeline(uiState, action);
|
||||
case OPEN_CABINET:
|
||||
return openCabinet(uiState, action);
|
||||
case CLOSE_CABINET:
|
||||
return closeCabinet(uiState, action);
|
||||
case TOGGLE_INFOPOPUP:
|
||||
return toggleInfoPopup(uiState, action);
|
||||
case TOGGLE_NOTIFICATIONS:
|
||||
return toggleNotifications(uiState, action);
|
||||
default:
|
||||
return uiState;
|
||||
}
|
||||
}
|
||||
|
||||
export default ui;
|
||||
18
src/reducers/utils/helpers.js
Normal file
18
src/reducers/utils/helpers.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export function parseDateTimes(arrayToParse) {
|
||||
const parsedArray = [];
|
||||
|
||||
arrayToParse.forEach(item => {
|
||||
let incoming_datetime = `${item.date}T00:00`;
|
||||
if (item.time) incoming_datetime = `${item.date}T${item.time}`;
|
||||
const parser = d3.timeParse(process.env.INCOMING_DATETIME_FORMAT);
|
||||
item.timestamp = d3.timeFormat("%Y-%m-%dT%H:%M:%S")(parser(incoming_datetime));
|
||||
|
||||
parsedArray.push(item);
|
||||
});
|
||||
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
export function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
75
src/reducers/utils/validators.js
Normal file
75
src/reducers/utils/validators.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
import eventSchema from '../schema/eventSchema.js';
|
||||
import categorySchema from '../schema/categorySchema.js';
|
||||
import siteSchema from '../schema/siteSchema.js';
|
||||
|
||||
import { capitalize } from './helpers.js';
|
||||
|
||||
/*
|
||||
* Create an error notification object
|
||||
* Types: ['error', 'warning', 'good', 'neural']
|
||||
*/
|
||||
function makeError(type, id, message) {
|
||||
return {
|
||||
type: 'error',
|
||||
id,
|
||||
message: `${type} ${id}: ${message}`
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate domain schema
|
||||
*/
|
||||
export function validate(domain) {
|
||||
const sanitizedDomain = {
|
||||
events: [],
|
||||
categories: [],
|
||||
sites: [],
|
||||
notifications: domain.notifications,
|
||||
tags: domain.tags
|
||||
}
|
||||
|
||||
const discardedDomain = {
|
||||
events: [],
|
||||
categories: [],
|
||||
sites: []
|
||||
}
|
||||
|
||||
function validateItem(item, domainClass, schema) {
|
||||
const result = Joi.validate(item, schema);
|
||||
if (result.error !== null) {
|
||||
const id = item.id || '-';
|
||||
const domainStr = capitalize(domainClass);
|
||||
const error = makeError(domainStr, id, result.error.message);
|
||||
|
||||
discardedDomain[domainClass].push(Object.assign(item, { error }));
|
||||
} else {
|
||||
sanitizedDomain[domainClass].push(item);
|
||||
}
|
||||
}
|
||||
|
||||
domain.events.forEach(event => {
|
||||
validateItem(event, 'events', eventSchema);
|
||||
});
|
||||
domain.categories.forEach(category => {
|
||||
validateItem(category, 'categories', categorySchema);
|
||||
});
|
||||
domain.sites.forEach(site => {
|
||||
validateItem(site, 'sites', siteSchema);
|
||||
});
|
||||
|
||||
// Message the number of failed items
|
||||
Object.keys(discardedDomain).forEach(disc => {
|
||||
const len = discardedDomain[disc].length;
|
||||
if (len) {
|
||||
sanitizedDomain.notifications.push({
|
||||
message: `${len} invalid ${disc} not displayed.`,
|
||||
items: discardedDomain[disc],
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
return sanitizedDomain;
|
||||
}
|
||||
Reference in New Issue
Block a user