Merge pull request #37 from forensic-architecture/fix/clean-semantics

Fix/clean semantics
This commit is contained in:
Lachlan Kermode
2018-12-06 14:31:42 +00:00
committed by GitHub
12 changed files with 165 additions and 183 deletions

View File

@@ -3,8 +3,8 @@ module.exports = {
SERVER_ROOT: 'http://localhost:4040',
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',
SOURCES_EXT: '/api/example/export_events/ids',
TAGS_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',

View File

@@ -1,8 +1,18 @@
// TODO: move to util lib
function urlFromEnv(ext) {
if (process.env[ext]) {
return `${process.env.SERVER_ROOT}${process.env[ext]}`
} else {
return null
}
}
// 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 = urlFromEnv('EVENT_EXT')
const CATEGORY_URL = urlFromEnv('CATEGORY_EXT')
const TAG_URL = urlFromEnv('TAGS_EXT')
const SOURCES_URL = urlFromEnv('SOURCES_EXT')
const SITES_URL = urlFromEnv('SITES_EXT')
const eventUrlMap = (event) => `${process.env.SERVER_ROOT}${process.env.EVENT_DESC_ROOT}/${(event.id) ? event.id : event}`
/*
@@ -97,110 +107,128 @@ export function updateDomain(domain) {
}
}
export function fetchEvents (events) {
export function fetchSelected(selected) {
if (!selected || !selected.length || selected.length === 0) {
console.log('hitting base')
return updateSelected([])
}
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
})
dispatch(updateSelected(selected))
if (!SOURCES_URL) {
dispatch(fetchSourceError('No source extension specified.'))
} else {
dispatch(toggleFetchingSources())
}
}
}
export const UPDATE_HIGHLIGHTED = 'UPDATE_HIGHLIGHTED'
export function updateHighlighted(highlighted) {
return {
type: UPDATE_HIGHLIGHTED,
highlighted: highlighted
}
return {
type: UPDATE_HIGHLIGHTED,
highlighted: highlighted
}
}
export const UPDATE_SELECTED = 'UPDATE_SELECTED'
export function updateSelected(selected) {
return {
type: UPDATE_SELECTED,
selected: selected
}
return {
type: UPDATE_SELECTED,
selected: selected
}
}
export const UPDATE_DISTRICT = 'UPDATE_DISTRICT'
export function updateDistrict(district) {
return {
type: UPDATE_DISTRICT,
district
}
return {
type: UPDATE_DISTRICT,
district
}
}
export const UPDATE_TAGFILTERS = 'UPDATE_TIMEFILTERS'
export function updateTagFilters(tag) {
return {
type: UPDATE_TAGFILTERS,
tag
}
return {
type: UPDATE_TAGFILTERS,
tag
}
}
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
export function updateTimeRange(timerange) {
return {
type: UPDATE_TIMERANGE,
timerange
};
return {
type: UPDATE_TIMERANGE,
timerange
}
}
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
export function resetAllFilters() {
return {
type: RESET_ALLFILTERS
}
return {
type: RESET_ALLFILTERS
}
}
// UI
export const TOGGLE_FETCHING_DOMAIN = 'TOGGLE_FETCHING_DOMAIN'
export function toggleFetchingDomain() {
return {
type: TOGGLE_FETCHING_DOMAIN
}
return {
type: TOGGLE_FETCHING_DOMAIN
}
}
export const TOGGLE_FETCHING_EVENTS = 'TOGGLE_FETCHING_EVENTS'
export function toggleFetchingEvents() {
return {
type: TOGGLE_FETCHING_EVENTS
}
export const TOGGLE_FETCHING_SOURCES = 'TOGGLE_FETCHING_SOURCES'
export function toggleFetchingSources() {
return {
type: TOGGLE_FETCHING_SOURCES
}
}
export const TOGGLE_LANGUAGE = 'TOGGLE_LANGUAGE';
export function toggleLanguage(language) {
return {
type: TOGGLE_LANGUAGE,
language,
}
return {
type: TOGGLE_LANGUAGE,
language,
}
}
export const CLOSE_TOOLBAR = 'CLOSE_TOOLBAR';
export function closeToolbar() {
return {
type: CLOSE_TOOLBAR
}
return {
type: CLOSE_TOOLBAR
}
}
export const TOGGLE_INFOPOPUP = 'TOGGLE_INFOPOPUP';
export function toggleInfoPopup() {
return {
type: TOGGLE_INFOPOPUP
}
return {
type: TOGGLE_INFOPOPUP
}
}
export const TOGGLE_NOTIFICATIONS = 'TOGGLE_NOTIFICATIONS'
export function toggleNotifications() {
return {
type: TOGGLE_NOTIFICATIONS
}
return {
type: TOGGLE_NOTIFICATIONS
}
}
export const MARK_NOTIFICATIONS_READ = 'MARK_NOTIFICATIONS_READ'
export function markNotificationsRead() {
return {
type: MARK_NOTIFICATIONS_READ
}
}
// ERRORS
export const FETCH_SOURCE_ERROR = 'FETCH_SOURCE_ERROR'
export function fetchSourceError(msg) {
return {
type: FETCH_SOURCE_ERROR,
msg
}
}

View File

@@ -22,7 +22,6 @@ class CardStack extends React.Component {
event={event}
language={this.props.language}
tools={this.props.tools}
isLoading={this.props.isLoading}
getNarrativeLinks={this.props.getNarrativeLinks}
getCategoryGroup={this.props.getCategoryGroup}
getCategoryColor={this.props.getCategoryColor}
@@ -54,15 +53,12 @@ class CardStack extends React.Component {
<div
id='card-stack-header'
className='card-stack-header'
onClick={() => this.props.onToggle('TOGGLE_CARDSTACK')}
onClick={() => this.props.onToggleCardstack()}
>
<button className="side-menu-burg is-active"><span></span></button>
<p className="header-copy top">
{(this.props.isLoading)
? copy[this.props.language].loading
: `${this.props.selected.length} ${header_lang}`}
{`${this.props.selected.length} ${header_lang}`}
</p>
{(this.props.isLoading) ? '' : this.renderLocation()}
</div>
)
}
@@ -71,10 +67,7 @@ class CardStack extends React.Component {
return (
<div id="card-stack-content" className="card-stack-content">
<ul>
{(this.props.isLoading)
? <Card language={this.props.language} isLoading={true} />
: this.renderCards()
}
{this.renderCards()}
</ul>
</div>
);
@@ -99,7 +92,7 @@ function mapStateToProps(state) {
language: state.app.language,
tools: state.ui.tools,
isCardstack: state.ui.flags.isCardstack,
isLoading: state.ui.flags.isFetchingEvents
isFetchingSources: state.ui.flags.isFetchingSources
}
}

View File

@@ -19,7 +19,7 @@ class Dashboard extends React.Component {
this.handleHighlight = this.handleHighlight.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleToggle = this.handleToggle.bind(this);
// this.handleToggle = this.handleToggle.bind(this);
this.handleTagFilter = this.handleTagFilter.bind(this);
this.updateTimerange = this.updateTimerange.bind(this);
@@ -46,32 +46,11 @@ class Dashboard extends React.Component {
handleSelect(selected) {
if (selected) {
let eventsToSelect = selected.map(event => this.getEventById(event.id));
const parser = this.props.ui.tools.parser;
const p = this.props.ui.tools.parser;
eventsToSelect = eventsToSelect.sort((a, b) => {
return parser(a.timestamp) - parser(b.timestamp);
});
eventsToSelect = eventsToSelect.sort((a, b) => p(a.timestamp) - p(b.timestamp))
if (eventsToSelect.every(event => (event))) {
this.props.actions.updateSelected(eventsToSelect);
}
// Now fetch detail data for each event
// Add transmitter and receiver data for coevents
this.props.actions.fetchEvents(selected)
.then((events) => {
let eventsSelected = events.map(ev => {
return Object.assign({}, ev, this.getEventById(ev.id));
});
eventsSelected = eventsSelected.sort((a, b) => {
return parser(a.timestamp) - parser(b.timestamp);
});
this.props.actions.updateSelected(eventsSelected);
});
} else {
this.props.actions.updateSelected([]);
this.props.actions.fetchSelected(eventsToSelect)
}
}
@@ -83,23 +62,6 @@ class Dashboard extends React.Component {
this.props.actions.updateTimeRange(timeRange);
}
handleToggle( key ) {
switch( key ) {
case 'TOGGLE_CARDSTACK': {
this.props.actions.updateSelected([]);
break;
}
case 'TOGGLE_INFOPOPUP': {
this.props.actions.toggleInfoPopup();
break;
}
case 'TOGGLE_NOTIFICATIONS': {
this.props.actions.toggleNotifications();
break;
}
}
}
getCategoryColor(category='other') {
return this.props.ui.style.categories[category] || this.props.style.categories['other']
}
@@ -113,41 +75,39 @@ class Dashboard extends React.Component {
render() {
return (
<div>
<Viewport
<Viewport
methods={{
select: this.handleSelect,
highlight: this.handleHighlight,
onSelect: this.handleSelect,
getCategoryColor: category => this.getCategoryColor(category)
}}
/>
<Toolbar
filter={this.handleTagFilter}
toggle={ (key) => this.handleToggle(key) }
onFilter={this.handleTagFilter}
actions={this.props.actions}
/>
<CardStack
onSelect={this.handleSelect}
onHighlight={this.handleHighlight}
onToggle={this.handleToggle}
onToggleCardstack={() => this.props.actions.updateSelected([])}
getNarrativeLinks={event => this.getNarrativeLinks(event)}
getCategoryColor={category => this.getCategoryColor(category)}
/>
<Timeline
onSelect={this.handleSelect}
onUpdateTimerange={this.updateTimerange}
// onHighlight={this.handleHighlight}
// onToggle={() => this.handleToggle('TOGGLE_CARDSTACK')}
getCategoryColor={category => this.getCategoryColor(category)}
methods={{
onSelect: this.handleSelect,
onUpdateTimerange: this.updateTimerange,
getCategoryColor: category => this.getCategoryColor(category)
}}
/>
<InfoPopUp
ui={this.props.ui}
app={this.props.app}
toggle={() => this.handleToggle('TOGGLE_INFOPOPUP')}
toggle={() => this.props.actions.toggleInfoPopup()}
/>
<Notification
isNotification={this.props.ui.flags.isNotification}
notifications={this.props.domain.notifications}
toggle={() => this.handleToggle('TOGGLE_NOTIFICATIONS')}
onToggle={this.props.actions.markNotificationsRead}
/>
<LoadingOverlay
ui={this.props.ui.flags.isFetchingDomain}

View File

@@ -43,8 +43,8 @@ export default class Notification extends React.Component{
}
render() {
if (this.props.isNotification) {
const notificationsToRender = this.props.notifications.filter(n => !('isRead' in n && n.isRead))
if (notificationsToRender.length > 0) {
return (
<div className={`notification-wrapper`}>
{this.props.notifications.map((notification) => {
@@ -52,7 +52,7 @@ export default class Notification extends React.Component{
return (
<div className='notification' onClick={() => this.toggleDetails() }>
<button
onClick={() => this.props.toggle()}
onClick={this.props.onToggle}
className="side-menu-burg over-white is-active"
>
<span />

View File

@@ -19,13 +19,7 @@ class Timeline extends React.Component {
dom: this.props.dom
}
const methods = {
onSelect: this.props.onSelect,
onUpdateTimerange: this.props.onUpdateTimerange,
getCategoryColor: this.props.getCategoryColor
}
this.timeline = new TimelineLogic(this.props.app, ui, methods);
this.timeline = new TimelineLogic(this.props.app, ui, this.props.methods);
this.timeline.update(this.props.domain, this.props.app);
this.timeline.render(this.props.domain);
}

View File

@@ -170,7 +170,7 @@ class Toolbar extends React.Component {
categories={this.props.categories}
tagFilters={this.props.tagFilters}
categoryFilters={this.props.categoryFilters}
filter={this.props.filter}
filter={this.props.onFilter}
title={title}
overview={overview}
language={this.props.language}
@@ -189,7 +189,7 @@ class Toolbar extends React.Component {
categories={this.props.categories}
tagFilters={this.props.tagFilters}
categoryFilters={this.props.categoryFilters}
filter={this.props.filter}
filter={this.props.onFilter}
/>
</TabPanel>
)

View File

@@ -21,7 +21,6 @@ export default function(newApp, ui, methods) {
}
const getCategoryColor = methods.getCategoryColor;
const select = methods.select;
const narrativeProps = ui.narratives;
// Map Settings
@@ -272,7 +271,7 @@ Stop and start the development process in terminal after you have added your tok
${lMap.latLngToLayerPoint(d.LatLng).y})`;
})
.on('click', (location) => {
select(location.events);
methods.onSelect(location.events);
});
const eventsDom = g.selectAll('.location')

View File

@@ -1,19 +1,32 @@
import initial from '../store/initial.js'
import { UPDATE_DOMAIN } from '../actions'
import { UPDATE_DOMAIN, MARK_NOTIFICATIONS_READ } from '../actions'
import { parseDateTimes } from './utils/helpers.js'
import { validate } from './utils/validators.js'
import { validateDomain } from './utils/validators.js'
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))
return {
...domainState,
...validateDomain(action.domain)
}
}
function markNotificationsRead (domainState, action) {
return {
...domainState,
notifications: domainState.notifications.map(n => ({ ...n, isRead: true }))
}
}
function domain (domainState = initial.domain, action) {
switch (action.type) {
case UPDATE_DOMAIN:
return updateDomain(domainState, action)
case MARK_NOTIFICATIONS_READ:
return markNotificationsRead(domainState, action)
default:
return domainState
}

View File

@@ -2,7 +2,7 @@ import initial from '../store/initial.js';
import {
TOGGLE_FETCHING_DOMAIN,
TOGGLE_FETCHING_EVENTS,
TOGGLE_FETCHING_SOURCES,
TOGGLE_VIEW,
TOGGLE_TIMELINE,
TOGGLE_INFOPOPUP,
@@ -10,49 +10,45 @@ import {
} from '../actions'
function toggleFetchingDomain(uiState, action) {
return Object.assign({}, uiState, {
flags: Object.assign({}, uiState.flags, {
return {
...uiState,
flags: {
...uiState.flags,
isFetchingDomain: !uiState.flags.isFetchingDomain
})
});
}
}
}
function toggleFetchingEvents(uiState, action) {
return Object.assign({}, uiState, {
flags: Object.assign({}, uiState.flags, {
isFetchingEvents: !uiState.flags.isFetchingEvents
})
});
function toggleFetchingSources(uiState, action) {
return {
...uiState,
flags: {
...uiState.flags,
isFetchingSources: !uiState.flags.isFetchingSources
}
}
}
function toggleInfoPopup(uiState, action) {
return Object.assign({}, uiState, {
flags: Object.assign({}, uiState.flags, {
return {
...uiState,
flags: {
...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);
return toggleFetchingDomain(uiState, action)
case TOGGLE_FETCHING_SOURCES:
return toggleFetchingSources(uiState, action)
case TOGGLE_INFOPOPUP:
return toggleInfoPopup(uiState, action);
case TOGGLE_NOTIFICATIONS:
return toggleNotifications(uiState, action);
return toggleInfoPopup(uiState, action)
default:
return uiState;
return uiState
}
}

View File

@@ -52,7 +52,7 @@ function validateTree (node, parent, set, duplicates) {
/*
* Validate domain schema
*/
export function validate(domain) {
export function validateDomain (domain) {
const sanitizedDomain = {
events: [],
categories: [],

View File

@@ -30,8 +30,8 @@ const initial = {
selected: [],
filters: {
timerange: [
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-22T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-27T12:00:00")
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00")
],
tags: [],
categories: [],
@@ -59,7 +59,7 @@ const initial = {
{
label: '3 días',
duration: 4320,
active: true
active: false
},
{
label: '12 horas',
@@ -120,7 +120,7 @@ const initial = {
narratives: {
default: {
style: 'dotted', // ['dotted', 'solid']
opacity: 0.4, // range between 0 and 1
opacity: 0.9, // range between 0 and 1
stroke: 'red', // Any hex or rgb code
strokeWidth: 2
},
@@ -139,11 +139,10 @@ const initial = {
},
flags: {
isFetchingDomain: false,
isFetchingEvents: false,
isFetchingSources: false,
isCardstack: true,
isInfopopup: false,
isNotification: true
isInfopopup: false
},
tools: {
formatter: d3.timeFormat("%d %b, %H:%M"),