Apply better naming and cleanup

This commit is contained in:
Franc Camps-Febrer
2018-11-14 13:57:18 -05:00
parent 24a8c9363c
commit 640baf904e
11 changed files with 218 additions and 269 deletions

View File

@@ -145,12 +145,12 @@ export function updateTagFilters(tag) {
}
}
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE'
export function updateTimeRange(range) {
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
export function updateTimeRange(timerange) {
return {
type: UPDATE_TIMERANGE,
range
}
timerange
};
}
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'

View File

@@ -58,30 +58,6 @@ class Card extends React.Component {
</div>);
}
// NB: is this function for a future feature?
renderIncidents() {
const incident_type_lang = copy[this.props.language].cardstack.incident_type;
const incidentTags = []; //this.props.event.tags.filter(tag => tag.type === 'incident_type');
return (<div className="event-card-section event-type">
<h4>{incident_type_lang}</h4>
{
incidentTags.map((tag, idx) => {
return (<span className={(
tag.name === 'contradicción' || tag.name === 'declaración con sospecha de tortura')
? ' flagged'
: ''}>
{tag.name}{
(idx < incidentTags.length - 1)
? ','
: ''
}
</span>);
})
}
</div>);
}
renderSummary() {
const summary = copy[this.props.language].cardstack.description;
const desc = this.props.event.description;
@@ -162,15 +138,6 @@ class Card extends React.Component {
}
}
renderHeader() {
return (<div className="card-collapsed">
{this.renderWarning()}
{this.renderCategory()}
{this.renderTimestamp()}
{this.renderSummary()}
</div>);
}
renderCardLink(event, direction) {
if (event !== null) {
const timelabel = this.getTimeLabel();
@@ -183,6 +150,7 @@ class Card extends React.Component {
renderNarrative() {
const links = this.props.getNarrativeLinks(this.props.event);
if (links !== null) {
return (<div className="event-card-section">
<h4>Connected events</h4>
@@ -192,6 +160,22 @@ class Card extends React.Component {
}
}
renderSpinner() {
return (<div className="spinner">
<div className="double-bounce1"></div>
<div className="double-bounce2"></div>
</div>);
}
renderHeader() {
return (<div className="card-collapsed">
{this.renderWarning()}
{this.renderCategory()}
{this.renderTimestamp()}
{this.renderSummary()}
</div>);
}
renderContent() {
if (this.state.isFolded) {
return (<div className="card-bottomhalf folded"></div>);
@@ -200,31 +184,15 @@ class Card extends React.Component {
{this.renderSpinner()}
</div>);
} else {
if (!this.props.event.hasOwnProperty('receiver') && !this.props.event.hasOwnProperty('transmitter')) {
return (<div className="card-bottomhalf">
{this.renderLocation()}
{this.renderTags()}
{this.renderSource()}
{this.renderNarrative()}
</div>);
} else {
return (<div className="card-bottomhalf">
{this.renderTags()}
{this.renderSource()}
{this.renderNarrative()}
</div>);
}
return (<div className="card-bottomhalf">
{this.renderLocation()}
{this.renderTags()}
{this.renderSource()}
{this.renderNarrative()}
</div>);
}
}
renderSpinner() {
return (<div className="spinner">
<div className="double-bounce1"></div>
<div className="double-bounce2"></div>
</div>);
}
renderArrow() {
let classes = (this.state.isFolded)
? 'arrow-down folded'

View File

@@ -38,11 +38,11 @@ class Dashboard extends React.Component {
handleSelect(selected) {
if (selected) {
// attacks are not susceptible to tag filters, so make sure this happens only when they are found
// in the domain
let eventsToSelect = selected.map(eventId => this.props.domain.events[eventId]);
const parser = this.props.ui.tools.parser;
eventsToSelect = eventsToSelect.sort((a, b) => {
return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
return parser(a.timestamp) - parser(b.timestamp);
});
if (eventsToSelect.every(event => (event))) {
@@ -58,7 +58,7 @@ class Dashboard extends React.Component {
});
eventsSelected = eventsSelected.sort((a, b) => {
return this.props.ui.tools.parser(a.timestamp) - this.props.ui.tools.parser(b.timestamp);
return parser(a.timestamp) - parser(b.timestamp);
});
this.props.actions.updateSelected(eventsSelected);
@@ -105,8 +105,8 @@ class Dashboard extends React.Component {
}
getCategoryLabel(category) {
const label = this.props.domain.categories.find(t => t.category === category).category_label;
return label;
const categories = this.props.domain.categories;
return categories.find(t => t.category === category).category_label;
}
getNarrativeLinks(event) {
@@ -176,7 +176,7 @@ class Dashboard extends React.Component {
narratives={this.props.domain.narratives}
categoryGroups={this.props.domain.categoryGroups}
range={this.props.app.filters.range}
timerange={this.props.app.filters.timerange}
selected={this.props.app.selected}
language={this.props.app.language}
@@ -202,7 +202,7 @@ class Dashboard extends React.Component {
toggle={() => this.handleToggle('TOGGLE_NOTIFICATIONS')}
/>
<LoadingOverlay
ui={this.props.ui}
ui={this.props.ui.flags.isFetchingDomain}
language={this.props.app.language}
/>
</div>
@@ -218,21 +218,23 @@ function mapStateToProps(state) {
return Object.assign({}, state, {
domain: Object.assign({}, state.domain, {
events: selectors.getFilteredEvents(state),
locations: selectors.getFilteredLocations(state),
categories: selectors.getFilteredCategories(state),
categoryGroups: selectors.getCategoryGroups(state),
sites: selectors.getSites(state),
tags: selectors.getAllTags(state),
narratives: selectors.getFilteredNarratives(state),
// These items are affected by app selectionFilters
events: selectors.selectEvents(state),
locations: selectors.selectLocations(state),
categories: selectors.selectCategories(state),
categoryGroups: selectors.selectCategoryGroups(state),
narratives: selectors.selectNarratives(state),
notifications: state.domain.notifications,
// These items are not affected by selectionFilters
sites: selectors.getSites(state),
tags: selectors.getTagTree(state),
notifications: selectors.getNotifications(state)
}),
app: Object.assign({}, state.app, {
error: state.app.error,
filters: Object.assign({}, state.app.filters, {
range: selectors.getRangeFilter(state),
tags: selectors.getTagFilters(state)
timerange: selectors.getTimeRange(state),
tags: selectors.selectTagList(state)
})
}),
ui: state.ui

View File

@@ -1,9 +1,9 @@
import React from 'react';
import copy from '../js/data/copy.json';
const LoadingOverlay = ({ ui, language }) => {
const LoadingOverlay = ({ isFetchingDomain, language }) => {
let classes = 'loading-overlay';
classes += (!ui.flags.isFetchingDomain) ? ' hidden' : '';
classes += (!isFetchingDomain) ? ' hidden' : '';
return (
<div id="loading-overlay" className={classes}>

View File

@@ -16,7 +16,7 @@ class Timeline extends React.Component {
categoryGroups: this.props.categoryGroups
}
const app = {
range: this.props.range,
timerange: this.props.timerange,
selected: this.props.selected,
language: this.props.language,
select: this.props.select,
@@ -43,7 +43,7 @@ class Timeline extends React.Component {
}
const app = {
range: nextProps.range,
timerange: nextProps.timerange,
selected: nextProps.selected,
language: nextProps.language,
select: nextProps.select,
@@ -75,8 +75,8 @@ class Timeline extends React.Component {
const labels_title_lang = copy[this.props.language].timeline.labels_title;
const info_lang = copy[this.props.language].timeline.info;
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`;
const date0 = this.props.tools.formatterWithYear(this.props.range[0]);
const date1 = this.props.tools.formatterWithYear(this.props.range[1]);
const date0 = this.props.tools.formatterWithYear(this.props.timerange[0]);
const date1 = this.props.tools.formatterWithYear(this.props.timerange[1]);
return (
<div className={classes}>
<div className="timeline-header">

View File

@@ -1,63 +0,0 @@
import '../scss/main.scss';
import React from 'react';
import Map from '../js/map/map.js';
import { areEqual } from '../js/data/utilities.js';
class View2D extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const domain = {
locations: this.props.locations,
narratives: this.props.narratives,
sites: this.props.sites,
categoryGroups: this.props.categoryGroups
}
const app = {
views: this.props.views,
selected: this.props.selected,
highlighted: this.props.highlighted,
getCategoryGroup: this.props.getCategoryGroup,
getCategoryGroupColor: this.props.getCategoryGroupColor,
mapAnchor: this.props.mapAnchor
}
const ui = {
style: this.props.uiStyle,
dom: this.props.dom
}
this.map = new Map(app, ui, this.props.select);
this.map.update(domain, app);
}
componentWillReceiveProps(nextProps) {
const domain = {
locations: nextProps.locations,
narratives: nextProps.narratives,
sites: nextProps.sites,
categoryGroups: nextProps.categoryGroups
}
const app = {
views: nextProps.views,
selected: nextProps.selected,
highlighted: nextProps.highlighted,
getCategoryGroup: nextProps.getCategoryGroup,
getCategoryGroupColor: nextProps.getCategoryGroupColor,
mapAnchor: this.props.mapAnchor
}
this.map.update(domain, app);
}
render() {
return (
<div className='map-wrapper'>
<div id="map" />
</div>
);
}
}
export default View2D;

View File

@@ -1,36 +1,62 @@
import '../scss/main.scss';
import React from 'react';
import View2D from './View2D.jsx';
import Map from '../js/map/map.js';
import { areEqual } from '../js/data/utilities.js';
class Viewport extends React.Component {
constructor(props) {
super(props);
}
render() {
if( this.props.isView2d ) {
return (
<View2D
locations={this.props.locations}
narratives={this.props.narratives}
sites={this.props.sites}
categoryGroups={this.props.categoryGroups}
views={this.props.views}
selected={this.props.selected}
highlighted={this.props.highlighted}
mapAnchor={this.props.mapAnchor}
uiStyle={this.props.uiStyle}
dom={this.props.dom}
select={this.props.select}
highlight={this.props.highlight}
getCategoryGroupColor={category => this.props.getCategoryGroupColor(category)}
getCategoryGroup={category => this.props.getCategoryGroup(category)}
/>
);
componentDidMount() {
const domain = {
locations: this.props.locations,
narratives: this.props.narratives,
sites: this.props.sites,
categoryGroups: this.props.categoryGroups
}
const app = {
views: this.props.views,
selected: this.props.selected,
highlighted: this.props.highlighted,
getCategoryGroup: this.props.getCategoryGroup,
getCategoryGroupColor: this.props.getCategoryGroupColor,
mapAnchor: this.props.mapAnchor
}
const ui = {
style: this.props.uiStyle,
dom: this.props.dom
}
this.map = new Map(app, ui, this.props.select);
this.map.update(domain, app);
}
componentWillReceiveProps(nextProps) {
const domain = {
locations: nextProps.locations,
narratives: nextProps.narratives,
sites: nextProps.sites,
categoryGroups: nextProps.categoryGroups
}
const app = {
views: nextProps.views,
selected: nextProps.selected,
highlighted: nextProps.highlighted,
getCategoryGroup: nextProps.getCategoryGroup,
getCategoryGroupColor: nextProps.getCategoryGroupColor,
mapAnchor: this.props.mapAnchor
}
this.map.update(domain, app);
}
render() {
return (
<div className='map-wrapper'>
<div id="map" />
</div>
);
}
}

View File

@@ -54,7 +54,7 @@ export default function(app, ui) {
let events = [];
let categoryGroups = [];
let selected = [];
let range = app.range;
let timerange = app.timerange;
const timeFilter = app.filter;
const select = app.select;
@@ -90,7 +90,7 @@ export default function(app, ui) {
const scale = {};
scale.x = d3.scaleTime()
.domain(range)
.domain(timerange)
.range([mg.l, WIDTH]);
const groupStep = (106 - 30) / categoryGroups.length;
@@ -222,8 +222,8 @@ export default function(app, ui) {
const dragNow = scale.x.invert(d3.event.x).getTime();
const timeShift = (drag0 - dragNow) / 1000;
const newDomain0 = d3.timeSecond.offset(range[0], timeShift);
const newDomainF = d3.timeSecond.offset(range[1], timeShift);
const newDomain0 = d3.timeSecond.offset(timerange[0], timeShift);
const newDomainF = d3.timeSecond.offset(timerange[1], timeShift);
scale.x.domain([newDomain0, newDomainF])
render();
@@ -417,7 +417,7 @@ export default function(app, ui) {
* It automatically sets brush timeline to a domain set by the params
*/
function updateTimeRange() {
scale.x.domain(range);
scale.x.domain(timerange);
axis.x0.scale(scale.x);
axis.x1.scale(scale.x);
}
@@ -430,12 +430,12 @@ export default function(app, ui) {
dom.axis.label0
.attr('x', 5)
.attr('y', 15)
.text(formatterWithYear(range[0]));
.text(formatterWithYear(timerange[0]));
dom.axis.label1
.attr('x', WIDTH - 5)
.attr('y', 15)
.text(formatterWithYear(range[1]))
.text(formatterWithYear(timerange[1]))
.style('text-anchor', 'end');
}
@@ -618,7 +618,7 @@ export default function(app, ui) {
renderAxis();
events = domain.events;
range = app.range;
timerange = app.timerange;
selected = app.selected.slice(0);
updateTimeRange();
}

View File

@@ -48,7 +48,7 @@ function updateTagFilters(appState, action) {
function updateTimeRange(appState, action) { // XXX
return Object.assign({}, appState, {
filters: Object.assign({}, appState.filters, {
range: action.range
timerange: action.timerange
}),
});
}
@@ -58,7 +58,7 @@ function resetAllFilters(appState) { // XXX
filters: Object.assign({}, appState.filters, {
tags: [],
categories: [],
range: [
timerange: [
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-25T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-09-28T12:00:00")
],

View File

@@ -10,61 +10,67 @@ export const getSites = (state) => {
if (process.env.features.USE_SITES) return state.domain.sites
return []
}
export const getAllTags = state => state.domain.tags
export const getNotifications = state => state.domain.notifications;
export const getTagTree = state => state.domain.tags;
export const getTagsFilter = state => state.app.filters.tags;
export const getTimeRange = state => state.app.filters.timerange;
export const getCategoriesFilter = state => state.app.filters.categories
export const getTagsFilter = state => state.app.filters.tags
export const getRangeFilter = state => state.app.filters.range
/**
* Some handy helpers
*/
const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
// NB: should we stick with the default semantics and name these as selectors?
// e.g. 'selectEvents', 'selectCoevents'.
// Filter events
function isTaggedIn (event, tagFilters) {
/**
* Given an event and all tags,
* returns true/false if event has any tag that is active
*/
function isTaggedIn(event, tagFilters) {
if (event.tags) {
const tagsArray = event.tags.split(',')
const isTagged = tagsArray.some((tag) => {
return tagFilters.find((tagFilter) => {
return (tagFilter.key === tag && tagFilter.active)
})
})
return isTagged
const tagsInEvent = event.tags.split(",");
const isTagged = tagsInEvent.some((tag) => {
return tagFilters.find(tF => (tF.key === tag && tF.active));
});
return isTagged;
} else {
return false
}
}
/**
* Given an event and a time range,
* returns true/false if the event falls within timeRange
*/
function isTimeRangedIn(event, timeRange) {
return (
timeRange[0] < parseTimestamp(event.timestamp)
&& parseTimestamp(event.timestamp) < timeRange[1]
);
}
function isNoTags(tagFilters) {
return (
tagFilters.length === 0
|| !process.env.features.USE_TAGS
|| tagFilters.every(t => !t.active)
);
}
/**
* Of all available events, selects those that fall within the time range,
* and if TAGS are being used, select them if their tags are enabled
*/
export const getFilteredEvents = createSelector(
[getEvents, getTagsFilter, getRangeFilter],
(events, tagFilters, rangeFilter) => {
return events.reduce((acc, value) => {
const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active))
export const selectEvents = createSelector(
[getEvents, getTagsFilter, getTimeRange],
(events, tagFilters, timeRange) => {
const isTagged = (noTags) || isTaggedIn(value, tagFilters)
return events.reduce((acc, event) => {
const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters);
const isTimeRanged = isTimeRangedIn(event, timeRange);
// TODO: put this datetime format as a constant
const isRange = (rangeFilter[0] < d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp)) &&
(d3.timeParse('%Y-%m-%dT%H:%M:%S')(value.timestamp) < rangeFilter[1])
<<<<<<< HEAD
if (isRange && isTagged) {
const event = Object.assign({}, value)
acc[event.id] = event
}
return acc
}, [])
}
)
=======
const isRange = (rangeFilter[0] < d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp)) &&
(d3.timeParse("%Y-%m-%dT%H:%M:%S")(value.timestamp) < rangeFilter[1]);
if (isRange && isTagged) {
const event = Object.assign({}, value);
acc[event.id] = event;
if (isTimeRanged && isTagged) {
const eventClone = Object.assign({}, event);
acc[event.id] = eventClone;
}
return acc;
@@ -76,21 +82,20 @@ export const getFilteredEvents = createSelector(
* Of all available events, selects those that fall within the time range,
* and if TAGS are being used, select them if their tags are enabled
*/
const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts);
export const getFilteredNarratives = createSelector(
[getEvents, getTagsFilter, getRangeFilter],
(events, tagFilters, rangeFilter) => {
export const selectNarratives = createSelector(
[getEvents, getTagsFilter, getTimeRange],
(events, tagFilters, timeRange) => {
const narratives = {};
events.forEach((evt) => {
const noTags = (tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active));
const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters);
const isTimeRanged = isTimeRangedIn(evt, timeRange);
const isInNarrative = evt.narrative;
const isTagged = (noTags) || isTaggedIn(evt, tagFilters);
const isRange = (rangeFilter[0] < parseTimestamp(evt.timestamp)) &&
(parseTimestamp(evt.timestamp) < rangeFilter[1]);
if (isRange && isTagged && evt.narrative) {
if (!narratives[evt.narrative]) narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
if (isTimeRanged && isTagged && isInNarrative) {
if (!narratives[evt.narrative]) {
narratives[evt.narrative] = { key: evt.narrative, steps: [], byId: {} };
}
narratives[evt.narrative].steps.push(evt);
narratives[evt.narrative].byId[evt.id] = { next: null, prev: null };
}
@@ -112,16 +117,18 @@ export const getFilteredNarratives = createSelector(
* Of all the filtered events, group them by location and return a list of
* locations with at least one event in it, based on the time range and tags
*/
export const getFilteredLocations = createSelector(
[getFilteredEvents],
export const selectLocations = createSelector(
[selectEvents],
(events) => {
const filteredLocations = {}
const selectedLocations = {};
events.forEach(event => {
const location = event.location
if (filteredLocations[location]) {
filteredLocations[location].events.push(event)
const location = event.location;
if (selectedLocations[location]) {
selectedLocations[location].events.push(event);
} else {
filteredLocations[location] = {
selectedLocations[location] = {
label: location,
events: [event],
latitude: event.latitude,
@@ -130,51 +137,60 @@ export const getFilteredLocations = createSelector(
}
})
// Make locations an array are remove if any are undefined
return Object.values(filteredLocations).filter(item => item)
}
)
// Make locations an array are remove if any are undefined
return Object.values(selectedLocations).filter(item => item);
});
// Filter categories
export const getFilteredCategories = createSelector(
/*
* Select categories, return them as a list
*/
export const selectCategories = createSelector(
[getCategories],
(categories) => Object.values(categories))
(categories) => {
return Object.values(categories);
}
);
/**
* Return categories by group
*/
export const getCategoryGroups = createSelector(
[getFilteredCategories],
export const selectCategoryGroups = createSelector(
[selectCategories],
(categories) => {
const groups = {}
categories.forEach((t) => { if (t.group && !groups[t.group]) { groups[t.group] = t.group_label } })
return Object.keys(groups).concat(['other'])
const groups = {};
categories.forEach((t) => {
if (t.group && !groups[t.group]) {
groups[t.group] = t.group_label;
}
});
return Object.keys(groups).concat(['other']);
}
)
);
/**
* Given a tree of tags, return those tags as a list, where each node has been
* aware of its depth, and given an 'active' flag
* Given a tree of tags, return those tags as a list
* Each node has been aware of its depth, and given an 'active' flag
*/
export const getTagFilters = createSelector(
[getAllTags],
export const selectTagList = createSelector(
[getTagTree],
(tags) => {
const allTagFilters = []
let depth = 0
function traverseNode (node, depth) {
node.active = (!node.hasOwnProperty('active')) ? false : node.active
node.depth = depth
if (node.active) allTagFilters.push(node)
depth = depth + 1
const tagList = [];
let depth = 0;
function traverseNode(node, depth) {
node.active = (!node.hasOwnProperty('active')) ? false : node.active;
node.depth = depth;
if (node.active) tagList.push(node)
if (Object.keys(node.children).length > 0) {
Object.values(node.children).forEach((childNode) => {
traverseNode(childNode, depth)
})
traverseNode(childNode, depth + 1);
});
}
}
if (tags && tags.key && tags.children) traverseNode(tags, depth)
return allTagFilters
if (tags.key && tags.children) traverseNode(tags, depth)
return tagList;
}
)

View File

@@ -7,7 +7,7 @@ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
const initial = {
domain: {
events: [],
narratives: [],
narratives: [],
locations: [],
categories: [],
@@ -23,7 +23,7 @@ const initial = {
selected: [],
notifications: [],
filters: {
range: [
timerange: [
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-22T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2014-08-27T12:00:00")
],