From 640baf904efa5d0d0d839c62d5bf628b0cdc2e38 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Wed, 14 Nov 2018 13:57:18 -0500 Subject: [PATCH] Apply better naming and cleanup --- src/actions/index.js | 8 +- src/components/Card.jsx | 78 ++++-------- src/components/Dashboard.jsx | 38 +++--- src/components/LoadingOverlay.jsx | 4 +- src/components/Timeline.jsx | 8 +- src/components/View2D.jsx | 63 ---------- src/components/Viewport.jsx | 74 ++++++++---- src/js/timeline/timeline.js | 16 +-- src/reducers/app.js | 4 +- src/selectors/index.js | 190 ++++++++++++++++-------------- src/store/initial.js | 4 +- 11 files changed, 218 insertions(+), 269 deletions(-) delete mode 100644 src/components/View2D.jsx diff --git a/src/actions/index.js b/src/actions/index.js index 9df2fe0..d701978 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -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' diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 01dab45..bb2864e 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -58,30 +58,6 @@ class Card extends React.Component { ); } - // 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 (
-

{incident_type_lang}

- { - incidentTags.map((tag, idx) => { - return ( - {tag.name}{ - (idx < incidentTags.length - 1) - ? ',' - : '' - } - ); - }) - } -
); - } - 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 (
- {this.renderWarning()} - {this.renderCategory()} - {this.renderTimestamp()} - {this.renderSummary()} -
); - } - 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 (

Connected events

@@ -192,6 +160,22 @@ class Card extends React.Component { } } + renderSpinner() { + return (
+
+
+
); + } + + renderHeader() { + return (
+ {this.renderWarning()} + {this.renderCategory()} + {this.renderTimestamp()} + {this.renderSummary()} +
); + } + renderContent() { if (this.state.isFolded) { return (
); @@ -200,31 +184,15 @@ class Card extends React.Component { {this.renderSpinner()}
); } else { - if (!this.props.event.hasOwnProperty('receiver') && !this.props.event.hasOwnProperty('transmitter')) { - return (
- {this.renderLocation()} - {this.renderTags()} - {this.renderSource()} - {this.renderNarrative()} -
); - } else { - return (
- {this.renderTags()} - {this.renderSource()} - {this.renderNarrative()} -
); - } + return (
+ {this.renderLocation()} + {this.renderTags()} + {this.renderSource()} + {this.renderNarrative()} +
); } } - - renderSpinner() { - return (
-
-
-
); - } - renderArrow() { let classes = (this.state.isFolded) ? 'arrow-down folded' diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 6018db0..7e31d31 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -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')} /> @@ -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 diff --git a/src/components/LoadingOverlay.jsx b/src/components/LoadingOverlay.jsx index ab7145e..ad455f3 100644 --- a/src/components/LoadingOverlay.jsx +++ b/src/components/LoadingOverlay.jsx @@ -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 (
diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index a7e3157..e388ad0 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -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 (
diff --git a/src/components/View2D.jsx b/src/components/View2D.jsx deleted file mode 100644 index c617f74..0000000 --- a/src/components/View2D.jsx +++ /dev/null @@ -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 ( -
-
-
- ); - } -} - -export default View2D; diff --git a/src/components/Viewport.jsx b/src/components/Viewport.jsx index e3620bb..2bac985 100644 --- a/src/components/Viewport.jsx +++ b/src/components/Viewport.jsx @@ -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 ( - 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 ( +
+
+
+ ); } } diff --git a/src/js/timeline/timeline.js b/src/js/timeline/timeline.js index 662b0ab..f29b20d 100644 --- a/src/js/timeline/timeline.js +++ b/src/js/timeline/timeline.js @@ -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(); } diff --git a/src/reducers/app.js b/src/reducers/app.js index d9f579b..d9bd94a 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -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") ], diff --git a/src/selectors/index.js b/src/selectors/index.js index 2114159..9be31ca 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -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; } ) diff --git a/src/store/initial.js b/src/store/initial.js index fd2e900..ba925fe 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -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") ],