mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
Add linting and cleaned up obvious suggestions
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
build/
|
||||
node_modules/
|
||||
config.js
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"dev": "webpack-dev-server --content-base static --mode development",
|
||||
"build": "NODE_ENV=production webpack --mode production",
|
||||
"test": "ava --verbose",
|
||||
"test-watch": "ava --watch"
|
||||
"test-watch": "ava --watch",
|
||||
"lint": "standard \"src/**/*.js\" \"test/**/*.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
@@ -48,6 +49,7 @@
|
||||
"node-sass": "^4.9.4",
|
||||
"redux-devtools": "^3.4.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"standard": "^12.0.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-cli": "^3.1.2",
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
/* global fetch, alert */
|
||||
import { urlFromEnv } from '../js/utilities'
|
||||
|
||||
const EVENT_DATA_URL = urlFromEnv('EVENT_EXT');
|
||||
const CATEGORY_URL = urlFromEnv('CATEGORY_EXT');
|
||||
const TAGS_URL = urlFromEnv('TAGS_EXT');
|
||||
const SOURCES_URL = urlFromEnv('SOURCES_EXT');
|
||||
const NARRATIVE_URL = urlFromEnv('NARRATIVE_EXT');
|
||||
const SITES_URL = urlFromEnv('SITES_EXT');
|
||||
const eventUrlMap = (event) => `${process.env.SERVER_ROOT}${process.env.EVENT_DESC_ROOT}/${(event.id) ? event.id : event}`;
|
||||
const EVENT_DATA_URL = urlFromEnv('EVENT_EXT')
|
||||
const CATEGORY_URL = urlFromEnv('CATEGORY_EXT')
|
||||
const TAGS_URL = urlFromEnv('TAGS_EXT')
|
||||
const SOURCES_URL = urlFromEnv('SOURCES_EXT')
|
||||
const NARRATIVE_URL = urlFromEnv('NARRATIVE_EXT')
|
||||
const SITES_URL = urlFromEnv('SITES_EXT')
|
||||
|
||||
const domainMsg = (domainType) => `Something went wrong fetching ${domainType}. Check the URL or try disabling them in the config file.`
|
||||
|
||||
|
||||
export function fetchDomain () {
|
||||
let notifications = []
|
||||
|
||||
@@ -24,7 +23,6 @@ export function fetchDomain () {
|
||||
|
||||
return dispatch => {
|
||||
dispatch(toggleFetchingDomain())
|
||||
const promises = []
|
||||
|
||||
const eventPromise = fetch(EVENT_DATA_URL)
|
||||
.then(response => response.json())
|
||||
@@ -96,27 +94,26 @@ export function fetchDomain () {
|
||||
// TODO: handle this appropriately in React hierarchy
|
||||
alert(err.message)
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const FETCH_ERROR = 'FETCH_ERROR'
|
||||
export function fetchError(message) {
|
||||
export function fetchError (message) {
|
||||
return {
|
||||
type: FETCH_ERROR,
|
||||
message,
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_DOMAIN = 'UPDATE_DOMAIN'
|
||||
export function updateDomain(domain) {
|
||||
export function updateDomain (domain) {
|
||||
return {
|
||||
type: UPDATE_DOMAIN,
|
||||
domain
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function fetchSource(source) {
|
||||
export function fetchSource (source) {
|
||||
return dispatch => {
|
||||
if (!SOURCES_URL) {
|
||||
dispatch(fetchSourceError('No source extension specified.'))
|
||||
@@ -135,14 +132,12 @@ export function fetchSource(source) {
|
||||
dispatch(fetchSourceError(err.message))
|
||||
dispatch(toggleFetchingSources())
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_HIGHLIGHTED = 'UPDATE_HIGHLIGHTED'
|
||||
export function updateHighlighted(highlighted) {
|
||||
export function updateHighlighted (highlighted) {
|
||||
return {
|
||||
type: UPDATE_HIGHLIGHTED,
|
||||
highlighted: highlighted
|
||||
@@ -150,7 +145,7 @@ export function updateHighlighted(highlighted) {
|
||||
}
|
||||
|
||||
export const UPDATE_SELECTED = 'UPDATE_SELECTED'
|
||||
export function updateSelected(selected) {
|
||||
export function updateSelected (selected) {
|
||||
return {
|
||||
type: UPDATE_SELECTED,
|
||||
selected: selected
|
||||
@@ -158,7 +153,7 @@ export function updateSelected(selected) {
|
||||
}
|
||||
|
||||
export const UPDATE_DISTRICT = 'UPDATE_DISTRICT'
|
||||
export function updateDistrict(district) {
|
||||
export function updateDistrict (district) {
|
||||
return {
|
||||
type: UPDATE_DISTRICT,
|
||||
district
|
||||
@@ -166,7 +161,7 @@ export function updateDistrict(district) {
|
||||
}
|
||||
|
||||
export const UPDATE_TAGFILTERS = 'UPDATE_TAGFILTERS'
|
||||
export function updateTagFilters(tag) {
|
||||
export function updateTagFilters (tag) {
|
||||
return {
|
||||
type: UPDATE_TAGFILTERS,
|
||||
tag
|
||||
@@ -174,52 +169,52 @@ export function updateTagFilters(tag) {
|
||||
}
|
||||
|
||||
export const UPDATE_CATEGORYFILTERS = 'UPDATE_CATEGORYFILTERS'
|
||||
export function updateCategoryFilters(category) {
|
||||
export function updateCategoryFilters (category) {
|
||||
return {
|
||||
type: UPDATE_CATEGORYFILTERS,
|
||||
category
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
|
||||
export function updateTimeRange(timerange) {
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE'
|
||||
export function updateTimeRange (timerange) {
|
||||
return {
|
||||
type: UPDATE_TIMERANGE,
|
||||
timerange
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE';
|
||||
export function updateNarrative(narrative) {
|
||||
export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE'
|
||||
export function updateNarrative (narrative) {
|
||||
return {
|
||||
type: UPDATE_NARRATIVE,
|
||||
narrative
|
||||
}
|
||||
}
|
||||
|
||||
export const INCREMENT_NARRATIVE_CURRENT = 'INCREMENT_NARRATIVE_CURRENT';
|
||||
export function incrementNarrativeCurrent() {
|
||||
export const INCREMENT_NARRATIVE_CURRENT = 'INCREMENT_NARRATIVE_CURRENT'
|
||||
export function incrementNarrativeCurrent () {
|
||||
return {
|
||||
type: INCREMENT_NARRATIVE_CURRENT
|
||||
}
|
||||
}
|
||||
|
||||
export const DECREMENT_NARRATIVE_CURRENT = 'DECREMENT_NARRATIVE_CURRENT';
|
||||
export function decrementNarrativeCurrent() {
|
||||
export const DECREMENT_NARRATIVE_CURRENT = 'DECREMENT_NARRATIVE_CURRENT'
|
||||
export function decrementNarrativeCurrent () {
|
||||
return {
|
||||
type: DECREMENT_NARRATIVE_CURRENT
|
||||
}
|
||||
}
|
||||
|
||||
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
|
||||
export function resetAllFilters() {
|
||||
export function resetAllFilters () {
|
||||
return {
|
||||
type: RESET_ALLFILTERS
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_SOURCE = "UPDATE_SOURCE"
|
||||
export function updateSource(source) {
|
||||
export const UPDATE_SOURCE = 'UPDATE_SOURCE'
|
||||
export function updateSource (source) {
|
||||
return {
|
||||
type: UPDATE_SOURCE,
|
||||
source
|
||||
@@ -229,65 +224,65 @@ export function updateSource(source) {
|
||||
// UI
|
||||
|
||||
export const TOGGLE_SITES = 'TOGGLE_SITES'
|
||||
export function toggleSites() {
|
||||
export function toggleSites () {
|
||||
return {
|
||||
type: TOGGLE_SITES
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_FETCHING_DOMAIN = 'TOGGLE_FETCHING_DOMAIN'
|
||||
export function toggleFetchingDomain() {
|
||||
export function toggleFetchingDomain () {
|
||||
return {
|
||||
type: TOGGLE_FETCHING_DOMAIN
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_FETCHING_SOURCES = 'TOGGLE_FETCHING_SOURCES'
|
||||
export function toggleFetchingSources() {
|
||||
export function toggleFetchingSources () {
|
||||
return {
|
||||
type: TOGGLE_FETCHING_SOURCES
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE';
|
||||
export function toggleLanguage(language) {
|
||||
export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE'
|
||||
export function toggleLanguage (language) {
|
||||
return {
|
||||
type: TOGGLE_LANGUAGE,
|
||||
language,
|
||||
language
|
||||
}
|
||||
}
|
||||
|
||||
export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR';
|
||||
export function closeToolbar() {
|
||||
export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR'
|
||||
export function closeToolbar () {
|
||||
return {
|
||||
type: CLOSE_TOOLBAR
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP';
|
||||
export function toggleInfoPopup() {
|
||||
export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP'
|
||||
export function toggleInfoPopup () {
|
||||
return {
|
||||
type: TOGGLE_INFOPOPUP
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_MAPVIEW = 'TOGGLE_MAPVIEW';
|
||||
export function toggleMapView(layer) {
|
||||
return {
|
||||
type: TOGGLE_MAPVIEW,
|
||||
layer
|
||||
}
|
||||
}
|
||||
export const TOGGLE_MAPVIEW = 'TOGGLE_MAPVIEW'
|
||||
export function toggleMapView (layer) {
|
||||
return {
|
||||
type: TOGGLE_MAPVIEW,
|
||||
layer
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_NOTIFICATIONS = 'TOGGLE_NOTIFICATIONS'
|
||||
export function toggleNotifications() {
|
||||
export function toggleNotifications () {
|
||||
return {
|
||||
type: TOGGLE_NOTIFICATIONS
|
||||
}
|
||||
}
|
||||
|
||||
export const MARK_NOTIFICATIONS_READ = 'MARK_NOTIFICATIONS_READ'
|
||||
export function markNotificationsRead() {
|
||||
export function markNotificationsRead () {
|
||||
return {
|
||||
type: MARK_NOTIFICATIONS_READ
|
||||
}
|
||||
@@ -296,7 +291,7 @@ export function markNotificationsRead() {
|
||||
// ERRORS
|
||||
|
||||
export const FETCH_SOURCE_ERROR = 'FETCH_SOURCE_ERROR'
|
||||
export function fetchSourceError(msg) {
|
||||
export function fetchSourceError (msg) {
|
||||
return {
|
||||
type: FETCH_SOURCE_ERROR,
|
||||
msg
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const CardCaret = ({ isHighlighted, toggle }) => {
|
||||
|
||||
let classes = (isHighlighted)
|
||||
? 'arrow-down'
|
||||
: 'arrow-down folded';
|
||||
: 'arrow-down folded'
|
||||
|
||||
return (
|
||||
<div className="card-toggle" onClick={toggle}>
|
||||
<div className='card-toggle' onClick={toggle}>
|
||||
<p>
|
||||
<i className={classes}></i>
|
||||
<i className={classes} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CardCaret;
|
||||
export default CardCaret
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import { capitalizeFirstLetter } from '../../js/utilities.js';
|
||||
import { capitalizeFirstLetter } from '../../js/utilities.js'
|
||||
|
||||
const CardCategory = ({ categoryTitle, categoryLabel, color }) => (
|
||||
<div className="card-row card-cell category">
|
||||
<div className='card-row card-cell category'>
|
||||
<h4>{categoryTitle}</h4>
|
||||
<p>
|
||||
{capitalizeFirstLetter(categoryLabel)}
|
||||
<span className='color-category' style={{ background: color }}/>
|
||||
<span className='color-category' style={{ background: color }} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default CardCategory;
|
||||
export default CardCategory
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../js/data/copy.json';
|
||||
import { isNotNullNorUndefined } from '../../js/utilities';
|
||||
import copy from '../../js/data/copy.json'
|
||||
import { isNotNullNorUndefined } from '../../js/utilities'
|
||||
|
||||
const CardLocation = ({ language, location }) => {
|
||||
|
||||
if (isNotNullNorUndefined(location)) {
|
||||
return (
|
||||
<div className="card-cell location">
|
||||
<div className='card-cell location'>
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
<i className='material-icons left'>location_on</i>
|
||||
{location}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
const unknown = copy[language].cardstack.unknown_location;
|
||||
const unknown = copy[language].cardstack.unknown_location
|
||||
return (
|
||||
<div className="card-cell location">
|
||||
<div className='card-cell location'>
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
<i className='material-icons left'>location_on</i>
|
||||
{unknown}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CardLocation;
|
||||
export default CardLocation
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import CardNarrativeLink from './CardNarrativeLink';
|
||||
import CardNarrativeLink from './CardNarrativeLink'
|
||||
|
||||
const CardNarrative = (props) => (
|
||||
<div className="card-row">
|
||||
<div className='card-row'>
|
||||
<h4>Connected events</h4>
|
||||
<div className="card-cell">
|
||||
<p>← <CardNarrativeLink {...props} event={props.next}/></p>
|
||||
<div className='card-cell'>
|
||||
<p>← <CardNarrativeLink {...props} event={props.next} /></p>
|
||||
<p>→ <CardNarrativeLink {...props} event={props.prev} /></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default CardNarrative;
|
||||
export default CardNarrative
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const CardNarrativeLink = ({ event, makeTimelabel, select }) => {
|
||||
if (event !== null) {
|
||||
const timelabel = makeTimelabel(event.timestamp);
|
||||
const timelabel = makeTimelabel(event.timestamp)
|
||||
|
||||
return (
|
||||
<a onClick={() => select(event)}>
|
||||
<small>{`${timelabel} / ${event.location}`}</small>
|
||||
</a>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (<a className="disabled"><small>None</small></a>);
|
||||
return (<a className='disabled'><small>None</small></a>)
|
||||
}
|
||||
|
||||
export default CardNarrativeLink;
|
||||
export default CardNarrativeLink
|
||||
|
||||
@@ -3,11 +3,9 @@ import PropTypes from 'prop-types'
|
||||
import Spinner from './Spinner'
|
||||
import Img from 'react-image'
|
||||
|
||||
import copy from '../../js/data/copy.json'
|
||||
|
||||
const CardSource = ({ source, isLoading, onClickHandler }) => {
|
||||
function renderIconText(type) {
|
||||
switch(type) {
|
||||
function renderIconText (type) {
|
||||
switch (type) {
|
||||
case 'Eyewitness Testimony':
|
||||
return 'visibility'
|
||||
case 'Government Data':
|
||||
@@ -29,7 +27,7 @@ const CardSource = ({ source, isLoading, onClickHandler }) => {
|
||||
|
||||
if (!source) {
|
||||
return (
|
||||
<div className="card-source">
|
||||
<div className='card-source'>
|
||||
<div>Error: this source was not found</div>
|
||||
</div>
|
||||
)
|
||||
@@ -45,30 +43,30 @@ const CardSource = ({ source, isLoading, onClickHandler }) => {
|
||||
}
|
||||
|
||||
const fallbackIcon = (
|
||||
<i className="material-icons source-icon">
|
||||
<i className='material-icons source-icon'>
|
||||
{renderIconText(source.type)}
|
||||
</i>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="card-source">
|
||||
<div className='card-source'>
|
||||
{isLoading
|
||||
? <Spinner/>
|
||||
: (
|
||||
<div className="source-row" onClick={() => onClickHandler(source)}>
|
||||
{!!thumbnail ? (
|
||||
<Img
|
||||
className="source-icon"
|
||||
src={thumbnail}
|
||||
loader={<Spinner small />}
|
||||
unloader={fallbackIcon}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
) : fallbackIcon}
|
||||
<p>{source.id}</p>
|
||||
</div>
|
||||
)}
|
||||
? <Spinner />
|
||||
: (
|
||||
<div className='source-row' onClick={() => onClickHandler(source)}>
|
||||
{thumbnail ? (
|
||||
<Img
|
||||
className='source-icon'
|
||||
src={thumbnail}
|
||||
loader={<Spinner small />}
|
||||
unloader={fallbackIcon}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
) : fallbackIcon}
|
||||
<p>{source.id}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -79,7 +77,7 @@ CardSource.propTypes = {
|
||||
type: PropTypes.string
|
||||
}),
|
||||
isLoading: PropTypes.bool,
|
||||
onClickHandler: PropTypes.func.isRequired,
|
||||
onClickHandler: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CardSource
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../js/data/copy.json';
|
||||
import copy from '../../js/data/copy.json'
|
||||
|
||||
const CardSummary = ({ language, description, isHighlighted }) => {
|
||||
|
||||
const summary = copy[language].cardstack.description;
|
||||
const summary = copy[language].cardstack.description
|
||||
|
||||
return (
|
||||
<div className="card-row summary">
|
||||
<div className="card-cell">
|
||||
<div className='card-row summary'>
|
||||
<div className='card-cell'>
|
||||
<h4>{summary}</h4>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CardSummary;
|
||||
export default CardSummary
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../js/data/copy.json';
|
||||
import copy from '../../js/data/copy.json'
|
||||
|
||||
const CardTags = ({ tags, language }) => {
|
||||
const tags_lang = copy[language].cardstack.tags;
|
||||
const no_tags_lang = copy[language].cardstack.notags;
|
||||
const tagsLang = copy[language].cardstack.tags
|
||||
const noTagsLang = copy[language].cardstack.notags
|
||||
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className="card-row card-cell tags">
|
||||
<h4>{tags_lang}:</h4>
|
||||
<div className='card-row card-cell tags'>
|
||||
<h4>{tagsLang}:</h4>
|
||||
<p>
|
||||
{tags.map((tag, idx) => {
|
||||
return (
|
||||
<span className="tag">
|
||||
<small>{tag.name}</small>
|
||||
{(idx < tags.length - 1)
|
||||
? ','
|
||||
: ''}
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<span className='tag'>
|
||||
<small>{tag.name}</small>
|
||||
{(idx < tags.length - 1)
|
||||
? ','
|
||||
: ''}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="card-row card-cell tags">
|
||||
<h4>{tags_lang}</h4>
|
||||
<p><small>{no_tags_lang}</small></p>
|
||||
<div className='card-row card-cell tags'>
|
||||
<h4>{tagsLang}</h4>
|
||||
<p><small>{noTagsLang}</small></p>
|
||||
</div>
|
||||
);
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default CardTags;
|
||||
export default CardTags
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../js/data/copy.json';
|
||||
import { isNotNullNorUndefined } from '../../js/utilities';
|
||||
import copy from '../../js/data/copy.json'
|
||||
import { isNotNullNorUndefined } from '../../js/utilities'
|
||||
|
||||
const CardTimestamp = ({ makeTimelabel, language, timestamp }) => {
|
||||
|
||||
const daytime_lang = copy[language].cardstack.timestamp;
|
||||
const estimated_lang = copy[language].cardstack.estimated;
|
||||
const unknown_lang = copy[language].cardstack.unknown_time;
|
||||
const unknownLang = copy[language].cardstack.unknown_time
|
||||
|
||||
if (isNotNullNorUndefined(timestamp)) {
|
||||
const timelabel = makeTimelabel(timestamp);
|
||||
const timelabel = makeTimelabel(timestamp)
|
||||
return (
|
||||
<div className="card-cell timestamp">
|
||||
<div className='card-cell timestamp'>
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
<i className='material-icons left'>today</i>
|
||||
{timelabel}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="card-cell timestamp">
|
||||
<div className='card-cell timestamp'>
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
{unknown_lang}
|
||||
<i className='material-icons left'>today</i>
|
||||
{unknownLang}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CardTimestamp;
|
||||
export default CardTimestamp
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
export default ({ label, isActive, onClickCheckbox }) => (
|
||||
<div className={(isActive) ? 'item active' : 'item'}>
|
||||
<span onClick={() => onClickCheckbox()}>{label}</span>
|
||||
<button onClick={() => onClickCheckbox()}>
|
||||
<div className="checkbox" />
|
||||
<div className='checkbox' />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
@@ -15,14 +15,12 @@ export default ({
|
||||
onClick={() => onSelect(events)}
|
||||
>
|
||||
<circle
|
||||
className="event"
|
||||
className='event'
|
||||
cx={0}
|
||||
cy={0}
|
||||
style={styleProps}
|
||||
r={5}
|
||||
>
|
||||
</circle>
|
||||
/>
|
||||
{ extraRender ? extraRender() : null }
|
||||
</g>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const CoeventIcon = ({ isEnabled, toggleMapViews }) => {
|
||||
|
||||
const classes = (isEnabled) ? 'action-button active disabled' : 'action-button disabled';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={sitesClass}
|
||||
onClick={() => toggleMapViews('coevents')}
|
||||
>
|
||||
<svg className="coevents" x="0px" y="0px" width="30px" height="20px" viewBox="0 0 30 20" enableBackground="new 0 0 30 20">
|
||||
<polygon stroke-linejoin="round" stroke-miterlimit="10" points="19.178,20 10.823,20 10.473,14.081
|
||||
10,13.396 10,6.084 20,6.084 20,13.396 19.445,14.021 "/>
|
||||
<rect className="no-fill" x="11.4" y="7.867" width="7.2" height="3.35"/>
|
||||
<line stroke-linejoin="round" stroke-miterlimit="10" x1="12.125" y1="1" x2="12.125" y2="5.35"/>
|
||||
<rect x="11.4" y="4.271" width="1.496" height="1.079"/>
|
||||
<rect x="17.104" y="4.271" width="1.496" height="1.079"/>
|
||||
<svg className='coevents' x='0px' y='0px' width='30px' height='20px' viewBox='0 0 30 20' enableBackground='new 0 0 30 20'>
|
||||
<polygon stroke-linejoin='round' stroke-miterlimit='10' points='19.178,20 10.823,20 10.473,14.081
|
||||
10,13.396 10,6.084 20,6.084 20,13.396 19.445,14.021 ' />
|
||||
<rect className='no-fill' x='11.4' y='7.867' width='7.2' height='3.35' />
|
||||
<line stroke-linejoin='round' stroke-miterlimit='10' x1='12.125' y1='1' x2='12.125' y2='5.35' />
|
||||
<rect x='11.4' y='4.271' width='1.496' height='1.079' />
|
||||
<rect x='17.104' y='4.271' width='1.496' height='1.079' />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CoeventIcon;
|
||||
export default CoeventIcon
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
const RefreshIcon = ({ }) => {
|
||||
import React from 'react'
|
||||
|
||||
const RefreshIcon = () => {
|
||||
return (
|
||||
<svg className="reset" x="0px" y="0px" width="25px" height="25px" viewBox="7.5 7.5 25 25" enableBackground="new 7.5 7.5 25 25">
|
||||
<path stroke-width="2" stroke-miterlimit="10" d="M28.822,16.386c1.354,3.219,0.898,7.064-1.5,9.924
|
||||
c-3.419,4.073-9.49,4.604-13.562,1.186c-4.073-3.417-4.604-9.49-1.187-13.562c1.987-2.368,4.874-3.54,7.74-3.433" />
|
||||
<polygon points="26.137,12.748 27.621,19.464 28.9,16.741 31.898,16.503" />
|
||||
<svg className='reset' x='0px' y='0px' width='25px' height='25px' viewBox='7.5 7.5 25 25' enableBackground='new 7.5 7.5 25 25'>
|
||||
<path stroke-width='2' stroke-miterlimit='10' d='M28.822,16.386c1.354,3.219,0.898,7.064-1.5,9.924
|
||||
c-3.419,4.073-9.49,4.604-13.562,1.186c-4.073-3.417-4.604-9.49-1.187-13.562c1.987-2.368,4.874-3.54,7.74-3.433' />
|
||||
<polygon points='26.137,12.748 27.621,19.464 28.9,16.741 31.898,16.503' />
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default RefreshIcon;
|
||||
export default RefreshIcon
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const RouteIcon = ({ isEnabled, toggleMapViews }) => {
|
||||
|
||||
const classes = (isEnabled) ? 'action-button active disabled' : 'action-button disabled';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={sitesClass}
|
||||
onClick={() => toggleMapViews('routes')}
|
||||
>
|
||||
<svg x="0px" y="0px" width="30px" height="20px" viewBox="0 0 30 20" enableBackground="new 0 0 30 20">
|
||||
<path d="M0.806,13.646h7.619c2.762,0,3-0.238,3-3v-0.414c0-2.762,0.301-3,3.246-3h14.523"/>
|
||||
<polyline points="16.671,9.228 19.103,7.233 16.671,5.237 "/>
|
||||
<svg x='0px' y='0px' width='30px' height='20px' viewBox='0 0 30 20' enableBackground='new 0 0 30 20'>
|
||||
<path d='M0.806,13.646h7.619c2.762,0,3-0.238,3-3v-0.414c0-2.762,0.301-3,3.246-3h14.523' />
|
||||
<polyline points='16.671,9.228 19.103,7.233 16.671,5.237 ' />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default RouteIcon;
|
||||
export default RouteIcon
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const SitesIcon = ({ isActive, isDisabled, onClickHandler }) => {
|
||||
let classes = (isActive) ? 'action-button enabled' : 'action-button';
|
||||
let classes = (isActive) ? 'action-button enabled' : 'action-button'
|
||||
if (isDisabled) {
|
||||
classes = 'action-button disabled'
|
||||
}
|
||||
@@ -11,11 +11,11 @@ const SitesIcon = ({ isActive, isDisabled, onClickHandler }) => {
|
||||
className={classes}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
<svg x="0px" y="0px" width="30px" height="20px" viewBox="0 0 30 20" enableBackground="new 0 0 30 20">
|
||||
<path d="M24.615,6.793H5.385c-2.761,0-3,0.239-3,3v0.414c0,2.762,0.239,3,3,3h7.621l1.996,2.432l1.996-2.432h7.618c2.762,0,3-0.238,3-3V9.793C27.615,7.032,27.377,6.793,24.615,6.793z"/>
|
||||
<svg x='0px' y='0px' width='30px' height='20px' viewBox='0 0 30 20' enableBackground='new 0 0 30 20'>
|
||||
<path d='M24.615,6.793H5.385c-2.761,0-3,0.239-3,3v0.414c0,2.762,0.239,3,3,3h7.621l1.996,2.432l1.996-2.432h7.618c2.762,0,3-0.238,3-3V9.793C27.615,7.032,27.377,6.793,24.615,6.793z' />
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default SitesIcon;
|
||||
export default SitesIcon
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React from 'react';
|
||||
import copy from '../../js/data/copy.json';
|
||||
import React from 'react'
|
||||
import copy from '../../js/data/copy.json'
|
||||
|
||||
const LoadingOverlay = ({ isLoading, language }) => {
|
||||
let classes = 'loading-overlay';
|
||||
classes += (!isLoading) ? ' hidden' : '';
|
||||
let classes = 'loading-overlay'
|
||||
classes += (!isLoading) ? ' hidden' : ''
|
||||
|
||||
return (
|
||||
<div id="loading-overlay" className={classes}>
|
||||
<div className="loading-wrapper">
|
||||
<span id="loading-text" className="text">{copy[language].loading}</span>
|
||||
<div className="spinner">
|
||||
<div className="double-bounce1" />
|
||||
<div className="double-bounce2" />
|
||||
<div id='loading-overlay' className={classes}>
|
||||
<div className='loading-wrapper'>
|
||||
<span id='loading-text' className='text'>{copy[language].loading}</span>
|
||||
<div className='spinner'>
|
||||
<div className='double-bounce1' />
|
||||
<div className='double-bounce2' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default LoadingOverlay;
|
||||
export default LoadingOverlay
|
||||
|
||||
@@ -3,12 +3,10 @@ import { connect } from 'react-redux'
|
||||
import { selectActiveNarrative } from '../../selectors'
|
||||
|
||||
function NarrativeCard ({ narrative }) {
|
||||
// no display if no narrative
|
||||
// no display if no narrative
|
||||
const { steps, current } = narrative
|
||||
|
||||
if (steps[current]) {
|
||||
const step = steps[current]
|
||||
|
||||
return (
|
||||
<div className='narrative-info'>
|
||||
<div className='narrative-info-header'>
|
||||
@@ -27,14 +25,14 @@ function NarrativeCard ({ narrative }) {
|
||||
<div className='narrative-info-desc'>
|
||||
<p>{narrative.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
narrative: selectActiveNarrative(state)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default ({ onClickHandler, closeMsg }) => {
|
||||
<button
|
||||
className='side-menu-burg is-active'
|
||||
>
|
||||
<span></span>
|
||||
<span />
|
||||
</button>
|
||||
<div className='close-text'>{closeMsg}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const NoSource = ({ failedUrls }) => {
|
||||
const NoSource = ({ failedUrls }) => {
|
||||
return (
|
||||
<div className="no-source-container">
|
||||
<div className="no-source-row">
|
||||
<div className='no-source-container'>
|
||||
<div className='no-source-row'>
|
||||
<p>
|
||||
<i className="material-icons no-source-icon">error</i>
|
||||
</p>
|
||||
<i className='material-icons no-source-icon'>error</i>
|
||||
</p>
|
||||
<p>No media found, as the original media has not yet been uploaded to the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NoSource;
|
||||
export default NoSource
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const Spinner = ({ small }) => {
|
||||
return (
|
||||
<div className={`spinner ${small ? 'small' : ''}`}>
|
||||
<div className="double-bounce-overlay"></div>
|
||||
<div className="double-bounce"></div>
|
||||
<div className='double-bounce-overlay' />
|
||||
<div className='double-bounce' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Spinner;
|
||||
export default Spinner
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineClip = ({ dims }) => (
|
||||
<clipPath id="clip">
|
||||
<clipPath id='clip'>
|
||||
<rect
|
||||
x="120"
|
||||
y="0"
|
||||
x='120'
|
||||
y='0'
|
||||
width={dims.width - dims.margin_left - dims.width_controls}
|
||||
height={dims.height - 25}
|
||||
>
|
||||
</rect>
|
||||
/>
|
||||
</clipPath>
|
||||
);
|
||||
)
|
||||
|
||||
export default TimelineClip;
|
||||
export default TimelineClip
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
import DatetimeDot from './DatetimeDot'
|
||||
|
||||
// return a list of lists, where each list corresponds to a single category
|
||||
function getDotsToRender(events) {
|
||||
function getDotsToRender (events) {
|
||||
// each datetime needs to render as many dots as there are distinct
|
||||
// categories in the events contained by the datetime.
|
||||
// To this end, eventsByCategory is an intermediate data structure that
|
||||
@@ -32,7 +32,7 @@ const TimelineEvents = ({
|
||||
transitionDuration,
|
||||
styleDatetime
|
||||
}) => {
|
||||
function renderDatetime(datetime) {
|
||||
function renderDatetime (datetime) {
|
||||
if (narrative) {
|
||||
const { steps } = narrative
|
||||
// check all events in the datetime before rendering in narrative
|
||||
@@ -41,7 +41,7 @@ const TimelineEvents = ({
|
||||
const event = datetime.events[i]
|
||||
if (steps.map(s => s.id).includes(event.id)) {
|
||||
isInNarrative = true
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +80,11 @@ const TimelineEvents = ({
|
||||
|
||||
return (
|
||||
<g
|
||||
clipPath={"url(#clip)"}
|
||||
clipPath={'url(#clip)'}
|
||||
>
|
||||
{datetimes.map(datetime => renderDatetime(datetime))}
|
||||
</g>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineEvents;
|
||||
export default TimelineEvents
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineHandles = ({ dims, onMoveTime }) => {
|
||||
|
||||
return (
|
||||
<g className="time-controls-inline">
|
||||
<g className='time-controls-inline'>
|
||||
<g
|
||||
transform={`translate(${dims.margin_left + 20}, 62)`}
|
||||
onClick={() => onMoveTime('backwards')}
|
||||
>
|
||||
<circle r="15"></circle>
|
||||
<path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z" transform="rotate(270)"></path>
|
||||
<circle r='15' />
|
||||
<path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(270)' />
|
||||
</g>
|
||||
<g
|
||||
transform={`translate(${dims.width - dims.width_controls - 20}, 62)`}
|
||||
onClick={() => onMoveTime('forward')}
|
||||
>
|
||||
<circle r="15"></circle>
|
||||
<path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z" transform="rotate(90)"></path>
|
||||
<circle r='15' />
|
||||
<path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(90)' />
|
||||
</g>
|
||||
</g>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default TimelineHandles;
|
||||
export default TimelineHandles
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineHeader = ({ title, date0, date1, onClick, hideInfo }) => (
|
||||
<div className='timeline-header'>
|
||||
<div className='timeline-toggle' onClick={() => onClick()}>
|
||||
<p><i className='arrow-down'></i></p>
|
||||
<p><i className='arrow-down' /></p>
|
||||
</div>
|
||||
<div className={`timeline-info ${hideInfo ? 'hidden' : ''}`}>
|
||||
<p>{title}</p>
|
||||
<p>{date0} - {date1}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default TimelineHeader;
|
||||
export default TimelineHeader
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { formatterWithYear } from '../../js/utilities.js';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineLabels = ({ dims, timelabels }) => {
|
||||
|
||||
return (
|
||||
<g>
|
||||
<line
|
||||
class="axisBoundaries"
|
||||
class='axisBoundaries'
|
||||
x1={dims.margin_left}
|
||||
x2={dims.margin_left}
|
||||
y1="10"
|
||||
y2="20"
|
||||
>
|
||||
</line>
|
||||
y1='10'
|
||||
y2='20'
|
||||
/>
|
||||
<line
|
||||
class="axisBoundaries"
|
||||
class='axisBoundaries'
|
||||
x1={dims.width - dims.width_controls}
|
||||
x2={dims.width - dims.width_controls}
|
||||
y1="10"
|
||||
y2="20"
|
||||
>
|
||||
</line>
|
||||
y1='10'
|
||||
y2='20'
|
||||
/>
|
||||
{/* <text */}
|
||||
{/* class="timeLabel0 timeLabel" */}
|
||||
{/* x="5" */}
|
||||
@@ -41,4 +36,4 @@ const TimelineLabels = ({ dims, timelabels }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineLabels;
|
||||
export default TimelineLabels
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineMarkers = ({ getEventX, getCategoryY, transitionDuration, selected }) => {
|
||||
function renderMarker(event) {
|
||||
function renderMarker (event) {
|
||||
return (
|
||||
<circle
|
||||
className="timeline-marker"
|
||||
className='timeline-marker'
|
||||
cx={0}
|
||||
cy={0}
|
||||
style={{
|
||||
@@ -13,19 +13,18 @@ const TimelineMarkers = ({ getEventX, getCategoryY, transitionDuration, selected
|
||||
'-moz-transition': 'none',
|
||||
'opacity': 0.9
|
||||
}}
|
||||
r="10"
|
||||
>
|
||||
</circle>
|
||||
r='10'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<g
|
||||
clipPath={"url(#clip)"}
|
||||
clipPath={'url(#clip)'}
|
||||
>
|
||||
{selected.map(event => renderMarker(event))}
|
||||
</g>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineMarkers;
|
||||
export default TimelineMarkers
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const TimelineZoomControls = ({ extent, zoomLevels, dims, onApplyZoom }) => {
|
||||
function renderZoom(zoom, idx) {
|
||||
function renderZoom (zoom, idx) {
|
||||
const isActive = (zoom.duration === extent)
|
||||
return (
|
||||
<text
|
||||
className={`zoom-level-button ${isActive ? 'active' : ''}`}
|
||||
x="60"
|
||||
x='60'
|
||||
y={(idx * 15) + 20}
|
||||
onClick={() => onApplyZoom(zoom)}
|
||||
>
|
||||
@@ -19,7 +19,7 @@ const TimelineZoomControls = ({ extent, zoomLevels, dims, onApplyZoom }) => {
|
||||
<g transform={`translate(${dims.width - dims.width_controls}, 0)`}>
|
||||
{zoomLevels.map((z, idx) => renderZoom(z, idx))}
|
||||
</g>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineZoomControls;
|
||||
export default TimelineZoomControls
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
/* global d3 */
|
||||
/**
|
||||
* Get URI params to start with predefined set of
|
||||
* https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
|
||||
* @param {string} name: name of paramater to search
|
||||
* @param {string} url: url passed as variable, defaults to window.location.href
|
||||
*/
|
||||
export function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, `\\$&`);
|
||||
export function getParameterByName (name, url) {
|
||||
if (!url) url = window.location.href
|
||||
name = name.replace(/[[\]]/g, `\\$&`)
|
||||
|
||||
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
|
||||
const results = regex.exec(url);
|
||||
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`)
|
||||
const results = regex.exec(url)
|
||||
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
if (!results) return null
|
||||
if (!results[2]) return ''
|
||||
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,61 +23,61 @@ export function getParameterByName(name, url) {
|
||||
* @param {array} arr1: array of numbers
|
||||
* @param {array} arr2: array of numbers
|
||||
*/
|
||||
export function areEqual(arr1, arr2) {
|
||||
return ((arr1.length === arr2.length) && arr1.every((element, index) => {
|
||||
return element === arr2[index];
|
||||
}));
|
||||
export function areEqual (arr1, arr2) {
|
||||
return ((arr1.length === arr2.length) && arr1.every((element, index) => {
|
||||
return element === arr2[index]
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the variable is neither null nor undefined
|
||||
* @param {object} variable
|
||||
*/
|
||||
export function isNotNullNorUndefined(variable) {
|
||||
return (typeof variable !== 'undefined' && variable !== null);
|
||||
export function isNotNullNorUndefined (variable) {
|
||||
return (typeof variable !== 'undefined' && variable !== null)
|
||||
}
|
||||
|
||||
/*
|
||||
* Capitalizes _only_ the first letter of a string
|
||||
* Taken from: https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript
|
||||
*/
|
||||
export function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
export function capitalizeFirstLetter (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
|
||||
export function trimAndEllipse(string, stringNum) {
|
||||
export function trimAndEllipse (string, stringNum) {
|
||||
if (string.length > stringNum) {
|
||||
return string.substring(0, 120) + '...'
|
||||
}
|
||||
return string;
|
||||
return string
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Date object given a datetime string of the format: "2016-09-10T07:00:00"
|
||||
* @param {string} datetime
|
||||
*/
|
||||
export function parseDate(datetime) {
|
||||
export function parseDate (datetime) {
|
||||
return new Date(datetime.slice(0, 4),
|
||||
datetime.slice(5, 7) - 1,
|
||||
datetime.slice(8, 10),
|
||||
datetime.slice(11, 13),
|
||||
datetime.slice(14, 16),
|
||||
datetime.slice(17, 19)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function formatterWithYear(datetime) {
|
||||
return d3.timeFormat("%d %b %Y, %H:%M")(datetime);
|
||||
export function formatterWithYear (datetime) {
|
||||
return d3.timeFormat('%d %b %Y, %H:%M')(datetime)
|
||||
}
|
||||
|
||||
export function formatter(datetime) {
|
||||
return d3.timeFormat("%d %b, %H:%M")(datetime);
|
||||
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 const parseTimestamp = ts => d3.timeParse('%Y-%m-%dT%H:%M:%S')(ts)
|
||||
|
||||
export function compareTimestamp (a, b) {
|
||||
return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp));
|
||||
return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +86,7 @@ export function compareTimestamp (a, b) {
|
||||
* source, call with two sets of parentheses:
|
||||
* const src = insetSourceFrom(sources)(anEvent)
|
||||
*/
|
||||
export function insetSourceFrom(allSources) {
|
||||
export function insetSourceFrom (allSources) {
|
||||
return (event) => {
|
||||
let sources
|
||||
if (!event.sources) {
|
||||
@@ -100,14 +101,13 @@ export function insetSourceFrom(allSources) {
|
||||
sources
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging function: put in place of a mapStateToProps function to
|
||||
* view that source modal by default
|
||||
*/
|
||||
export function injectSource(id) {
|
||||
export function injectSource (id) {
|
||||
return state => {
|
||||
return {
|
||||
...state,
|
||||
@@ -119,7 +119,7 @@ export function injectSource(id) {
|
||||
}
|
||||
}
|
||||
|
||||
export function urlFromEnv(ext) {
|
||||
export function urlFromEnv (ext) {
|
||||
if (process.env[ext]) {
|
||||
return `${process.env.SERVER_ROOT}${process.env[ext]}`
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global d3 */
|
||||
import initial from '../store/initial.js'
|
||||
import { parseDate } from '../js/utilities'
|
||||
|
||||
@@ -20,22 +21,22 @@ import {
|
||||
TOGGLE_INFOPOPUP,
|
||||
TOGGLE_NOTIFICATIONS,
|
||||
FETCH_ERROR,
|
||||
FETCH_SOURCE_ERROR,
|
||||
FETCH_SOURCE_ERROR
|
||||
} from '../actions'
|
||||
|
||||
function updateHighlighted(appState, action) {
|
||||
function updateHighlighted (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
highlighted: action.highlighted
|
||||
})
|
||||
}
|
||||
|
||||
function updateSelected(appState, action) {
|
||||
function updateSelected (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
selected: action.selected
|
||||
})
|
||||
}
|
||||
|
||||
function updateNarrative(appState, action) {
|
||||
function updateNarrative (appState, action) {
|
||||
let minTime = appState.filters.timerange[0]
|
||||
let maxTime = appState.filters.timerange[1]
|
||||
|
||||
@@ -43,7 +44,7 @@ function updateNarrative(appState, action) {
|
||||
let cornerBound1 = [-180, -180]
|
||||
|
||||
// Compute narrative time range and map bounds
|
||||
if (!!action.narrative) {
|
||||
if (action.narrative) {
|
||||
minTime = parseDate('2100-01-01T00:00:00')
|
||||
maxTime = parseDate('1900-01-01T00:00:00')
|
||||
|
||||
@@ -85,7 +86,7 @@ function updateNarrative(appState, action) {
|
||||
...appState,
|
||||
narrative: action.narrative,
|
||||
narrativeState: {
|
||||
current: !!action.narrative ? 0 : null
|
||||
current: action.narrative ? 0 : null
|
||||
},
|
||||
filters: {
|
||||
...appState.filters,
|
||||
@@ -95,29 +96,33 @@ function updateNarrative(appState, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function incrementNarrativeCurrent(appState, action) {
|
||||
function incrementNarrativeCurrent (appState, action) {
|
||||
appState.narrativeState.current += 1
|
||||
|
||||
return {
|
||||
...appState,
|
||||
narrativeState: {
|
||||
current: appState.narrativeState.current += 1
|
||||
current: appState.narrativeState.current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decrementNarrativeCurrent(appState, action) {
|
||||
function decrementNarrativeCurrent (appState, action) {
|
||||
appState.narrativeState.current -= 1
|
||||
|
||||
return {
|
||||
...appState,
|
||||
narrativeState: {
|
||||
current: appState.narrativeState.current -= 1
|
||||
current: appState.narrativeState.current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTagFilters(appState, action) {
|
||||
function updateTagFilters (appState, action) {
|
||||
const tagFilters = appState.filters.tags.slice(0)
|
||||
const nextActiveState = action.tag.active
|
||||
|
||||
function traverseNode(node) {
|
||||
function traverseNode (node) {
|
||||
const tagFilter = tagFilters.find(tF => tF.key === node.key)
|
||||
node.active = nextActiveState
|
||||
if (!tagFilter) tagFilters.push(node)
|
||||
@@ -136,7 +141,7 @@ function updateTagFilters(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function updateCategoryFilters(appState, action) {
|
||||
function updateCategoryFilters (appState, action) {
|
||||
const categoryFilters = appState.filters.categories.slice(0)
|
||||
|
||||
const catFilter = categoryFilters.find(cF => cF.category === action.category.category)
|
||||
@@ -147,7 +152,6 @@ function updateCategoryFilters(appState, action) {
|
||||
catFilter.active = (!!action.category.active)
|
||||
}
|
||||
|
||||
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
categories: categoryFilters
|
||||
@@ -155,36 +159,36 @@ function updateCategoryFilters(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function updateTimeRange(appState, action) { // XXX
|
||||
function updateTimeRange (appState, action) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
timerange: action.timerange
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function resetAllFilters(appState) { // XXX
|
||||
function resetAllFilters (appState) { // XXX
|
||||
return Object.assign({}, appState, {
|
||||
filters: Object.assign({}, appState.filters, {
|
||||
tags: [],
|
||||
categories: [],
|
||||
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")
|
||||
],
|
||||
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: [],
|
||||
selected: []
|
||||
})
|
||||
}
|
||||
|
||||
function toggleLanguage(appState, action) {
|
||||
function toggleLanguage (appState, action) {
|
||||
let otherLanguage = (appState.language === 'es-MX') ? 'en-US' : 'es-MX'
|
||||
return Object.assign({}, appState, {
|
||||
language: action.language || otherLanguage
|
||||
})
|
||||
}
|
||||
|
||||
function toggleMapView(appState, action) {
|
||||
function toggleMapView (appState, action) {
|
||||
const isLayerInView = !appState.views[layer]
|
||||
const newViews = {}
|
||||
newViews[layer] = isLayerInView
|
||||
@@ -196,7 +200,7 @@ function toggleMapView(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function toggleSites(appState, action) {
|
||||
function toggleSites (appState, action) {
|
||||
return {
|
||||
...appState,
|
||||
flags: {
|
||||
@@ -206,14 +210,14 @@ function toggleSites(appState, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateSource(appState, action) {
|
||||
function updateSource (appState, action) {
|
||||
return {
|
||||
...appState,
|
||||
source: action.source
|
||||
}
|
||||
}
|
||||
|
||||
function fetchError(state, action) {
|
||||
function fetchError (state, action) {
|
||||
return {
|
||||
...state,
|
||||
error: action.message,
|
||||
@@ -221,7 +225,7 @@ function fetchError(state, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFetchingDomain(appState, action) {
|
||||
function toggleFetchingDomain (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
flags: Object.assign({}, appState.flags, {
|
||||
isFetchingDomain: !appState.flags.isFetchingDomain
|
||||
@@ -229,7 +233,7 @@ function toggleFetchingDomain(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function toggleFetchingSources(appState, action) {
|
||||
function toggleFetchingSources (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
flags: Object.assign({}, appState.flags, {
|
||||
isFetchingSources: !appState.flags.isFetchingSources
|
||||
@@ -237,7 +241,7 @@ function toggleFetchingSources(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function toggleInfoPopup(appState, action) {
|
||||
function toggleInfoPopup (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
flags: Object.assign({}, appState.flags, {
|
||||
isInfopopup: !appState.flags.isInfopopup
|
||||
@@ -245,7 +249,7 @@ function toggleInfoPopup(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function toggleNotifications(appState, action) {
|
||||
function toggleNotifications (appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
flags: Object.assign({}, appState.flags, {
|
||||
isNotification: !appState.flags.isNotification
|
||||
@@ -253,7 +257,7 @@ function toggleNotifications(appState, action) {
|
||||
})
|
||||
}
|
||||
|
||||
function fetchSourceError(appState, action) {
|
||||
function fetchSourceError (appState, action) {
|
||||
return {
|
||||
...appState,
|
||||
errors: {
|
||||
@@ -263,9 +267,7 @@ function fetchSourceError(appState, action) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function app(appState = initial.app, action) {
|
||||
function app (appState = initial.app, action) {
|
||||
switch (action.type) {
|
||||
case UPDATE_HIGHLIGHTED:
|
||||
return updateHighlighted(appState, action)
|
||||
|
||||
@@ -8,4 +8,4 @@ export default combineReducers({
|
||||
app,
|
||||
domain,
|
||||
ui
|
||||
});;
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Joi from 'joi';
|
||||
import Joi from 'joi'
|
||||
|
||||
const categorySchema = Joi.object().keys({
|
||||
category: Joi.string().required(),
|
||||
description: Joi.string(),
|
||||
});
|
||||
category: Joi.string().required(),
|
||||
description: Joi.string()
|
||||
})
|
||||
|
||||
export default categorySchema;
|
||||
export default categorySchema
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import Joi from 'joi';
|
||||
import Joi from 'joi'
|
||||
|
||||
const eventSchema = Joi.object().keys({
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
date: Joi.string().allow(''),
|
||||
time: Joi.string().allow(''),
|
||||
time_precision: Joi.string().allow(''),
|
||||
location: Joi.string().allow(''),
|
||||
latitude: Joi.string().allow(''),
|
||||
longitude: Joi.string().allow(''),
|
||||
type: Joi.string().allow(''),
|
||||
category: Joi.string().required(),
|
||||
narratives: Joi.array(),
|
||||
sources: Joi.array(),
|
||||
tags: Joi.array().allow(''),
|
||||
comments: Joi.string().allow(''),
|
||||
timestamp: Joi.string(),
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
date: Joi.string().allow(''),
|
||||
time: Joi.string().allow(''),
|
||||
time_precision: Joi.string().allow(''),
|
||||
location: Joi.string().allow(''),
|
||||
latitude: Joi.string().allow(''),
|
||||
longitude: Joi.string().allow(''),
|
||||
type: Joi.string().allow(''),
|
||||
category: Joi.string().required(),
|
||||
narratives: Joi.array(),
|
||||
sources: Joi.array(),
|
||||
tags: Joi.array().allow(''),
|
||||
comments: Joi.string().allow(''),
|
||||
timestamp: Joi.string(),
|
||||
|
||||
// nested
|
||||
narrative___stepStyles: Joi.array(),
|
||||
// nested
|
||||
narrative___stepStyles: Joi.array()
|
||||
})
|
||||
.and('latitude', 'longitude')
|
||||
.and('date', 'time', 'timestamp')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Joi from 'joi';
|
||||
import Joi from 'joi'
|
||||
|
||||
const narrativeSchema = Joi.object().keys({
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
label: Joi.string().required()
|
||||
});
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
label: Joi.string().required()
|
||||
})
|
||||
|
||||
export default narrativeSchema;
|
||||
export default narrativeSchema
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Joi from 'joi';
|
||||
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()
|
||||
});
|
||||
id: Joi.string().required(),
|
||||
description: Joi.string().allow('').required(),
|
||||
site: Joi.string().required(),
|
||||
latitude: Joi.string().required(),
|
||||
longitude: Joi.string().required()
|
||||
})
|
||||
|
||||
export default siteSchema;
|
||||
export default siteSchema
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import Joi from 'joi';
|
||||
import Joi from 'joi'
|
||||
|
||||
const sourceSchema = Joi.object().keys({
|
||||
id: Joi.string().required(),
|
||||
title: Joi.string().allow(''),
|
||||
thumbnail: Joi.string().allow(''),
|
||||
paths: Joi.array().required(),
|
||||
type: Joi.string().allow(''),
|
||||
affil_s: Joi.array().allow(''),
|
||||
url: Joi.string().allow(''),
|
||||
desc: Joi.string().allow(''),
|
||||
parent: Joi.string().allow(''),
|
||||
author: Joi.string().allow(''),
|
||||
date: Joi.string().allow(''),
|
||||
notes: Joi.string().allow('')
|
||||
});
|
||||
id: Joi.string().required(),
|
||||
title: Joi.string().allow(''),
|
||||
thumbnail: Joi.string().allow(''),
|
||||
paths: Joi.array().required(),
|
||||
type: Joi.string().allow(''),
|
||||
affil_s: Joi.array().allow(''),
|
||||
url: Joi.string().allow(''),
|
||||
desc: Joi.string().allow(''),
|
||||
parent: Joi.string().allow(''),
|
||||
author: Joi.string().allow(''),
|
||||
date: Joi.string().allow(''),
|
||||
notes: Joi.string().allow('')
|
||||
})
|
||||
|
||||
export default sourceSchema;
|
||||
export default sourceSchema
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import initial from '../store/initial.js';
|
||||
import initial from '../store/initial.js'
|
||||
|
||||
import {} from '../actions'
|
||||
|
||||
function ui(uiState = initial.ui, action) {
|
||||
return uiState;
|
||||
function ui (uiState = initial.ui, action) {
|
||||
return uiState
|
||||
}
|
||||
|
||||
export default ui;
|
||||
export default ui
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
export function parseDateTimes(arrayToParse) {
|
||||
const parsedArray = [];
|
||||
/* global d3 */
|
||||
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));
|
||||
let incomingDateTime = `${item.date}T00:00`
|
||||
if (item.time) incomingDateTime = `${item.date}T${item.time}`
|
||||
const parser = d3.timeParse(process.env.incomingDateTime_FORMAT)
|
||||
item.timestamp = d3.timeFormat('%Y-%m-%dT%H:%M:%S')(parser(incomingDateTime))
|
||||
|
||||
parsedArray.push(item);
|
||||
});
|
||||
parsedArray.push(item)
|
||||
})
|
||||
|
||||
return parsedArray;
|
||||
return parsedArray
|
||||
}
|
||||
|
||||
export function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
export function capitalize (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import Joi from 'joi';
|
||||
import Joi from 'joi'
|
||||
|
||||
import eventSchema from '../schema/eventSchema';
|
||||
import categorySchema from '../schema/categorySchema';
|
||||
import siteSchema from '../schema/siteSchema';
|
||||
import narrativeSchema from '../schema/narrativeSchema';
|
||||
import eventSchema from '../schema/eventSchema'
|
||||
import categorySchema from '../schema/categorySchema'
|
||||
import siteSchema from '../schema/siteSchema'
|
||||
import narrativeSchema from '../schema/narrativeSchema'
|
||||
import sourceSchema from '../schema/sourceSchema'
|
||||
|
||||
import { capitalize } from './helpers.js';
|
||||
import { capitalize } from './helpers.js'
|
||||
|
||||
/*
|
||||
* Create an error notification object
|
||||
* Types: ['error', 'warning', 'good', 'neural']
|
||||
*/
|
||||
function makeError(type, id, message) {
|
||||
function makeError (type, id, message) {
|
||||
return {
|
||||
type: 'error',
|
||||
id,
|
||||
@@ -20,10 +20,8 @@ function makeError(type, id, message) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const isLeaf = node => (Object.keys(node.children).length === 0);
|
||||
const isDuplicate = (node, set) => { return (set.has(node.key)); };
|
||||
|
||||
const isLeaf = node => (Object.keys(node.children).length === 0)
|
||||
const isDuplicate = (node, set) => { return (set.has(node.key)) }
|
||||
|
||||
/*
|
||||
* Traverse a tag tree and check its duplicates
|
||||
@@ -70,29 +68,29 @@ export function validateDomain (domain) {
|
||||
categories: [],
|
||||
sites: [],
|
||||
narratives: [],
|
||||
sources: [],
|
||||
sources: []
|
||||
}
|
||||
|
||||
function validateArrayItem(item, domainKey, schema) {
|
||||
const result = Joi.validate(item, schema);
|
||||
function validateArrayItem (item, domainKey, schema) {
|
||||
const result = Joi.validate(item, schema)
|
||||
if (result.error !== null) {
|
||||
const id = item.id || '-';
|
||||
const domainStr = capitalize(domainKey);
|
||||
const error = makeError(domainStr, id, result.error.message);
|
||||
const id = item.id || '-'
|
||||
const domainStr = capitalize(domainKey)
|
||||
const error = makeError(domainStr, id, result.error.message)
|
||||
|
||||
discardedDomain[domainKey].push(Object.assign(item, { error }));
|
||||
discardedDomain[domainKey].push(Object.assign(item, { error }))
|
||||
} else {
|
||||
sanitizedDomain[domainKey].push(item);
|
||||
sanitizedDomain[domainKey].push(item)
|
||||
}
|
||||
}
|
||||
|
||||
function validateArray(items, domainKey, schema) {
|
||||
function validateArray (items, domainKey, schema) {
|
||||
items.forEach(item => {
|
||||
validateArrayItem(item, domainKey, schema)
|
||||
})
|
||||
}
|
||||
|
||||
function validateObject(obj, domainKey, itemSchema) {
|
||||
function validateObject (obj, domainKey, itemSchema) {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const vl = obj[key]
|
||||
const result = Joi.validate(vl, itemSchema)
|
||||
@@ -109,29 +107,28 @@ export function validateDomain (domain) {
|
||||
})
|
||||
}
|
||||
|
||||
validateArray(domain.events, 'events', eventSchema);
|
||||
validateArray(domain.categories, 'categories', categorySchema);
|
||||
validateArray(domain.sites, 'sites', siteSchema);
|
||||
validateArray(domain.narratives, 'narratives', narrativeSchema);
|
||||
validateObject(domain.sources, 'sources', sourceSchema);
|
||||
|
||||
validateArray(domain.events, 'events', eventSchema)
|
||||
validateArray(domain.categories, 'categories', categorySchema)
|
||||
validateArray(domain.sites, 'sites', siteSchema)
|
||||
validateArray(domain.narratives, 'narratives', narrativeSchema)
|
||||
validateObject(domain.sources, 'sources', sourceSchema)
|
||||
|
||||
// Message the number of failed items in domain
|
||||
Object.keys(discardedDomain).forEach(disc => {
|
||||
const len = discardedDomain[disc].length;
|
||||
const len = discardedDomain[disc].length
|
||||
if (len) {
|
||||
sanitizedDomain.notifications.push({
|
||||
message: `${len} invalid ${disc} not displayed.`,
|
||||
items: discardedDomain[disc],
|
||||
type: 'error'
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Validate uniqueness of tags
|
||||
const tagSet = new Set([]);
|
||||
const duplicateTags = [];
|
||||
validateTree(domain.tags, {}, tagSet, duplicateTags);
|
||||
const tagSet = new Set([])
|
||||
const duplicateTags = []
|
||||
validateTree(domain.tags, {}, tagSet, duplicateTags)
|
||||
|
||||
// Duplicated tags
|
||||
if (duplicateTags.length > 0) {
|
||||
@@ -139,9 +136,9 @@ export function validateDomain (domain) {
|
||||
message: `Tags are required to be unique. Ignoring duplicates for now.`,
|
||||
items: duplicateTags,
|
||||
type: 'error'
|
||||
});
|
||||
})
|
||||
}
|
||||
sanitizedDomain.tags = domain.tags;
|
||||
sanitizedDomain.tags = domain.tags
|
||||
|
||||
return sanitizedDomain;
|
||||
return sanitizedDomain
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSelector} from 'reselect'
|
||||
import { createSelector } from 'reselect'
|
||||
import { parseTimestamp, compareTimestamp, insetSourceFrom } from '../js/utilities'
|
||||
|
||||
// Input selectors
|
||||
@@ -23,7 +23,6 @@ export const getTagsFilter = state => state.app.filters.tags
|
||||
export const getCategoriesFilter = state => state.app.filters.categories
|
||||
export const getTimeRange = state => state.app.filters.timerange
|
||||
|
||||
|
||||
/**
|
||||
* Some handy helpers
|
||||
*/
|
||||
@@ -32,7 +31,7 @@ export const getTimeRange = state => state.app.filters.timerange
|
||||
* Given an event and all tags,
|
||||
* returns true/false if event has any tag that is active
|
||||
*/
|
||||
function isTaggedIn(event, tagFilters) {
|
||||
function isTaggedIn (event, tagFilters) {
|
||||
if (event.tags) {
|
||||
const isTagged = event.tags.some((tag) => {
|
||||
return tagFilters.find(tF => (tF.key === tag && tF.active))
|
||||
@@ -47,10 +46,10 @@ function isTaggedIn(event, tagFilters) {
|
||||
* Given an event and all categories,
|
||||
* returns true/false if event has a category that is active
|
||||
*/
|
||||
function isTaggedInWithCategory(event, categories) {
|
||||
function isTaggedInWithCategory (event, categories) {
|
||||
if (event.category) {
|
||||
if (categories.find(c => (c.category === event.category && c.active))) return true
|
||||
return false;
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -59,22 +58,22 @@ function isTaggedInWithCategory(event, categories) {
|
||||
/*
|
||||
* Returns true if no tags are selected
|
||||
*/
|
||||
function isNoTags(tagFilters) {
|
||||
function isNoTags (tagFilters) {
|
||||
return (
|
||||
tagFilters.length === 0
|
||||
|| !process.env.features.USE_TAGS
|
||||
|| tagFilters.every(t => !t.active)
|
||||
tagFilters.length === 0 ||
|
||||
!process.env.features.USE_TAGS ||
|
||||
tagFilters.every(t => !t.active)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if no categories are selected
|
||||
*/
|
||||
function isNoCategories(categories) {
|
||||
function isNoCategories (categories) {
|
||||
return (
|
||||
categories.length === 0
|
||||
|| !process.env.features.CATEGORIES_AS_TAGS
|
||||
|| categories.every(c => !c.active)
|
||||
categories.length === 0 ||
|
||||
!process.env.features.CATEGORIES_AS_TAGS ||
|
||||
categories.every(c => !c.active)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -82,10 +81,10 @@ function isNoCategories(categories) {
|
||||
* Given an event and a time range,
|
||||
* returns true/false if the event falls within timeRange
|
||||
*/
|
||||
function isTimeRangedIn(event, timeRange) {
|
||||
function isTimeRangedIn (event, timeRange) {
|
||||
return (
|
||||
timeRange[0] < parseTimestamp(event.timestamp)
|
||||
&& parseTimestamp(event.timestamp) < timeRange[1]
|
||||
timeRange[0] < parseTimestamp(event.timestamp) &&
|
||||
parseTimestamp(event.timestamp) < timeRange[1]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,76 +93,73 @@ function isTimeRangedIn(event, timeRange) {
|
||||
* and if TAGS are being used, select them if their tags are enabled
|
||||
*/
|
||||
export const selectEvents = createSelector(
|
||||
[getEvents, getTagsFilter, getCategoriesFilter, getTimeRange],
|
||||
(events, tagFilters, categories, timeRange) => {
|
||||
[getEvents, getTagsFilter, getCategoriesFilter, getTimeRange],
|
||||
(events, tagFilters, categories, timeRange) => {
|
||||
return events.reduce((acc, event) => {
|
||||
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters)
|
||||
const isTaggedWithCategory = isTaggedInWithCategory(event, categories) || isNoCategories(categories)
|
||||
const isTimeRanged = isTimeRangedIn(event, timeRange)
|
||||
|
||||
return events.reduce((acc, event) => {
|
||||
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters)
|
||||
const isTaggedWithCategory = isTaggedInWithCategory(event, categories) || isNoCategories(categories)
|
||||
const isTimeRanged = isTimeRangedIn(event, timeRange)
|
||||
if (isTimeRanged && isTagged && isTaggedWithCategory) {
|
||||
const eventClone = Object.assign({}, event)
|
||||
acc[event.id] = eventClone
|
||||
}
|
||||
|
||||
if (isTimeRanged && isTagged && isTaggedWithCategory) {
|
||||
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,
|
||||
* and if TAGS are being used, select them if their tags are enabled
|
||||
*/
|
||||
export const selectNarratives = createSelector(
|
||||
[getEvents, getNarratives, getSources],
|
||||
(events, narrativesMeta, sources) => {
|
||||
[getEvents, getNarratives, getSources],
|
||||
(events, narrativesMeta, sources) => {
|
||||
const narratives = {}
|
||||
const narrativeSkeleton = id => ({ id, steps: [] })
|
||||
|
||||
const narratives = {}
|
||||
const narrativeSkeleton = id => ({ id, steps: [] })
|
||||
/* populate narratives dict with events */
|
||||
events.forEach(evt => {
|
||||
const isInNarrative = evt.narratives.length > 0
|
||||
|
||||
/* populate narratives dict with events */
|
||||
events.forEach(evt => {
|
||||
const isInNarrative = evt.narratives.length > 0
|
||||
evt.narratives.forEach(narrative => {
|
||||
// initialise
|
||||
if (!narratives[narrative]) { narratives[narrative] = narrativeSkeleton(narrative) }
|
||||
|
||||
evt.narratives.forEach(narrative => {
|
||||
// initialise
|
||||
if (!narratives[narrative])
|
||||
narratives[narrative] = narrativeSkeleton(narrative)
|
||||
|
||||
// add evt to steps
|
||||
if (isInNarrative)
|
||||
// NB: insetSourceFrom is a 'curried' function to allow with maps
|
||||
narratives[narrative].steps.push(insetSourceFrom(sources)(evt))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
/* sort steps by time */
|
||||
Object.keys(narratives).forEach(key => {
|
||||
const steps = narratives[key].steps
|
||||
|
||||
steps.sort(compareTimestamp)
|
||||
|
||||
if (narrativesMeta.find(n => n.id === key)) {
|
||||
narratives[key] = {
|
||||
...narrativesMeta.find(n => n.id === key),
|
||||
...narratives[key]
|
||||
}
|
||||
// add evt to steps
|
||||
if (isInNarrative) {
|
||||
// NB: insetSourceFrom is a 'curried' function to allow with maps
|
||||
narratives[narrative].steps.push(insetSourceFrom(sources)(evt))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Return narratives in original order
|
||||
// + filter those that are undefined
|
||||
return narrativesMeta.map(n => narratives[n.id]).filter(d => d);
|
||||
})
|
||||
/* sort steps by time */
|
||||
Object.keys(narratives).forEach(key => {
|
||||
const steps = narratives[key].steps
|
||||
|
||||
steps.sort(compareTimestamp)
|
||||
|
||||
if (narrativesMeta.find(n => n.id === key)) {
|
||||
narratives[key] = {
|
||||
...narrativesMeta.find(n => n.id === key),
|
||||
...narratives[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Return narratives in original order
|
||||
// + filter those that are undefined
|
||||
return narrativesMeta.map(n => narratives[n.id]).filter(d => d)
|
||||
})
|
||||
|
||||
/** 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) => narrative
|
||||
? { ...narrative, current }
|
||||
: null
|
||||
)
|
||||
@@ -180,7 +176,6 @@ export const selectActiveNarrative = createSelector(
|
||||
export const selectLocations = createSelector(
|
||||
[selectEvents],
|
||||
(events) => {
|
||||
|
||||
const selectedLocations = {}
|
||||
events.forEach(event => {
|
||||
const location = event.location
|
||||
@@ -230,7 +225,6 @@ export const selectDatetimes = createSelector(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Of all the sources, select those that are relevant to the selected events.
|
||||
*/
|
||||
@@ -253,12 +247,11 @@ export const selectCategories = createSelector(
|
||||
(categories) => {
|
||||
categories.map(cat => {
|
||||
cat.active = (!cat.hasOwnProperty('active')) ? false : cat.active
|
||||
});
|
||||
return categories;
|
||||
})
|
||||
return categories
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Given a tree of tags, return those tags as a list
|
||||
* Each node has been aware of its depth, and given an 'active' flag
|
||||
@@ -268,7 +261,7 @@ export const selectTagList = createSelector(
|
||||
(tags) => {
|
||||
const tagList = []
|
||||
let depth = 0
|
||||
function traverseNode(node, depth) {
|
||||
function traverseNode (node, depth) {
|
||||
node.active = (!node.hasOwnProperty('active')) ? false : node.active
|
||||
node.depth = depth
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { createStore, applyMiddleware, compose } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
|
||||
import rootReducer from '../reducers';
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
import rootReducer from '../reducers'
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
composeEnhancers(applyMiddleware(thunk))
|
||||
);
|
||||
)
|
||||
|
||||
export default store;
|
||||
export default store
|
||||
|
||||
@@ -16,7 +16,7 @@ const initial = {
|
||||
sources: {},
|
||||
sites: [],
|
||||
tags: {},
|
||||
notifications: [],
|
||||
notifications: []
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -29,7 +29,7 @@ const initial = {
|
||||
*/
|
||||
app: {
|
||||
errors: {
|
||||
source: null,
|
||||
source: null
|
||||
},
|
||||
highlighted: null,
|
||||
selected: [],
|
||||
@@ -51,7 +51,7 @@ const initial = {
|
||||
coevents: false,
|
||||
routes: false,
|
||||
sites: true
|
||||
},
|
||||
}
|
||||
},
|
||||
isMobile: (/Mobi/.test(navigator.userAgent)),
|
||||
language: 'en-US',
|
||||
@@ -83,7 +83,7 @@ const initial = {
|
||||
ui: {
|
||||
style: {
|
||||
categories: {
|
||||
default: '#f3de2c',
|
||||
default: '#f3de2c'
|
||||
},
|
||||
narratives: {
|
||||
default: {
|
||||
@@ -94,16 +94,16 @@ const initial = {
|
||||
}
|
||||
},
|
||||
dom: {
|
||||
timeline: "timeline",
|
||||
timeslider: "timeslider",
|
||||
map: "map"
|
||||
},
|
||||
timeline: 'timeline',
|
||||
timeslider: 'timeslider',
|
||||
map: 'map'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let appStore;
|
||||
let appStore
|
||||
if (process.env.store) {
|
||||
appStore = mergeDeepLeft(process.env.store, initial);
|
||||
appStore = mergeDeepLeft(process.env.store, initial)
|
||||
} else {
|
||||
appStore = initial
|
||||
}
|
||||
|
||||
@@ -1,68 +1,65 @@
|
||||
var assert = require('assert');
|
||||
var child_process = require('child_process')
|
||||
var http = require('http');
|
||||
import test from 'ava'
|
||||
var childProcess = require('childProcess')
|
||||
var http = require('http')
|
||||
|
||||
const SERVER_LAUNCH_WAIT_TIME = 5 * 1000;
|
||||
const SERVER_LAUNCH_WAIT_TIME = 5 * 1000
|
||||
|
||||
var server_proc = null;
|
||||
var server_exited = false;
|
||||
var serverProc = null
|
||||
var serverExited = false
|
||||
|
||||
test.before.cb(t => {
|
||||
console.log("launching server...")
|
||||
server_proc = child_process.spawn('yarn', ['dev'], {
|
||||
console.log('launching server...')
|
||||
serverProc = childProcess.spawn('yarn', ['dev'], {
|
||||
cwd: '.',
|
||||
stdio: 'ignore'
|
||||
});
|
||||
})
|
||||
|
||||
server_proc.on('exit', function(code, signal) {
|
||||
server_exited = true;
|
||||
});
|
||||
serverProc.on('exit', function (code, signal) {
|
||||
serverExited = true
|
||||
})
|
||||
|
||||
setTimeout(t.end, SERVER_LAUNCH_WAIT_TIME)
|
||||
});
|
||||
})
|
||||
|
||||
test.after(function() {
|
||||
console.log("killing server...")
|
||||
server_proc.kill('SIGKILL');
|
||||
});
|
||||
test.after(function () {
|
||||
console.log('killing server...')
|
||||
serverProc.kill('SIGKILL')
|
||||
})
|
||||
|
||||
test('should launch', t => {
|
||||
t.false(server_exited);
|
||||
});
|
||||
t.false(serverExited)
|
||||
})
|
||||
|
||||
var urls = [
|
||||
'/',
|
||||
'js/index.bundle.js'
|
||||
];
|
||||
]
|
||||
|
||||
urls.forEach(function(url) {
|
||||
urls.forEach(function (url) {
|
||||
test.cb('should respond to request for "' + url + '"', t => {
|
||||
http.get({
|
||||
hostname: 'localhost',
|
||||
port: 8080,
|
||||
path: '/',
|
||||
agent: false
|
||||
}, function(res) {
|
||||
var result_data = '';
|
||||
}, function (res) {
|
||||
var resultData = ''
|
||||
|
||||
if(res.statusCode != 200) {
|
||||
t.fail('Server response was not 200.');
|
||||
if (res.statusCode !== 200) {
|
||||
t.fail('Server response was not 200.')
|
||||
} else {
|
||||
res.on('data', function(data) { result_data += data });
|
||||
res.on('data', function (data) { resultData += data })
|
||||
|
||||
res.on('end', function() {
|
||||
if (result_data.length > 0) {
|
||||
t.pass();
|
||||
res.on('end', function () {
|
||||
if (resultData.length > 0) {
|
||||
t.pass()
|
||||
} else {
|
||||
t.fail("Server returned no data.");
|
||||
t.fail('Server returned no data.')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
t.end();
|
||||
t.end()
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user