From 0ea109968c45efa920916e9163230df2a1aef42d Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 25 Aug 2020 08:52:04 -0700 Subject: [PATCH 01/25] Pulling in associations correctly; condensed filters and narratives into associations --- example.config.js | 13 +++----- src/actions/index.js | 51 ++++++++++++----------------- src/reducers/validate/validators.js | 1 + 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/example.config.js b/example.config.js index 8eafa14..ac443c1 100644 --- a/example.config.js +++ b/example.config.js @@ -4,9 +4,8 @@ module.exports = { SERVER_ROOT: 'http://localhost:4040', EVENTS_EXT: '/api/timemap_data/export_events/deeprows', CATEGORIES_EXT: '/api/timemap_data/export_categories/rows', - FILTERS_EXT: '/api/timemap_data/export_filters/tree', + ASSOCIATIONS_EXT: '/api/timemap_data/export_associations/deeprows', SOURCES_EXT: '/api/timemap_data/export_sources/deepids', - NARRATIVE_EXT: '', SITES_EXT: '', SHAPES_EXT: '', DATE_FMT: 'MM/DD/YYYY', @@ -24,13 +23,11 @@ module.exports = { } }, features: { - USE_CATEGORIES: false, - CATEGORIES_AS_FILTERS: false, - USE_FILTERS: true, - FILTERS_AS_NARRATIVES: false, - USE_NARRATIVES: false, + USE_CATEGORIES: true, + CATEGORIES_AS_FILTERS: true, + USE_ASSOCIATIONS: true, USE_SOURCES: true, - USE_COVER: false, + USE_COVER: true, USE_SEARCH: false, USE_SITES: false, USE_SHAPES: false, diff --git a/src/actions/index.js b/src/actions/index.js index 44be864..f1541bc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -5,9 +5,8 @@ import { urlFromEnv } from '../common/utilities' // const CONFIG_URL = urlFromEnv('CONFIG_EXT') const EVENT_DATA_URL = urlFromEnv('EVENTS_EXT') const CATEGORY_URL = urlFromEnv('CATEGORIES_EXT') -const FILTERS_URL = urlFromEnv('FILTERS_EXT') +const ASSOCIATIONS_URL = urlFromEnv('ASSOCIATIONS_EXT') const SOURCES_URL = urlFromEnv('SOURCES_EXT') -const NARRATIVE_URL = urlFromEnv('NARRATIVES_EXT') const SITES_URL = urlFromEnv('SITES_EXT') const SHAPES_URL = urlFromEnv('SHAPES_EXT') @@ -50,31 +49,18 @@ export function fetchDomain () { .catch(() => handleError(domainMsg('categories'))) } - let narPromise = Promise.resolve([]) - if (features.USE_NARRATIVES) { - narPromise = fetch(NARRATIVE_URL) - .then(response => response.json()) - .catch(() => handleError(domainMsg('narratives'))) - } - - let sitesPromise = Promise.resolve([]) - if (features.USE_SITES) { - sitesPromise = fetch(SITES_URL) - .then(response => response.json()) - .catch(() => handleError(domainMsg('sites'))) - } - - let filtersPromise = Promise.resolve([]) - if (features.USE_FILTERS) { - if (!FILTERS_URL) { - filtersPromise = Promise.resolve(handleError('USE_FILTERS is true, but you have not provided a FILTERS_EXT')) + let associationsPromise = Promise.resolve([]) + if (features.USE_ASSOCIATIONS) { + if (!ASSOCIATIONS_URL) { + associationsPromise = Promise.resolve(handleError('USE_ASSOCIATIONS is true, but you have not provided a ASSOCIATIONS_EXT')) } else { - filtersPromise = fetch(FILTERS_URL) + associationsPromise = fetch(ASSOCIATIONS_URL) .then(response => response.json()) - .catch(() => handleError(domainMsg('filters'))) + .catch(() => handleError(domainMsg('associations'))) } } + let sourcesPromise = Promise.resolve([]) if (features.USE_SOURCES) { if (!SOURCES_URL) { @@ -86,6 +72,13 @@ export function fetchDomain () { } } + let sitesPromise = Promise.resolve([]) + if (features.USE_SITES) { + sitesPromise = fetch(SITES_URL) + .then(response => response.json()) + .catch(() => handleError(domainMsg('sites'))) + } + let shapesPromise = Promise.resolve([]) if (features.USE_SHAPES) { shapesPromise = fetch(SHAPES_URL) @@ -96,21 +89,19 @@ export function fetchDomain () { return Promise.all([ eventPromise, catPromise, - narPromise, - sitesPromise, - filtersPromise, + associationsPromise, sourcesPromise, + sitesPromise, shapesPromise ]) .then(response => { const result = { events: response[0], categories: response[1], - narratives: response[2], - sites: response[3], - filters: response[4], - sources: response[5], - shapes: response[6], + associations: response[2], + sources: response[3], + sites: response[4], + shapes: response[5], notifications } if (Object.values(result).some(resp => resp.hasOwnProperty('error'))) { diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js index 05e6dbb..2fcb7be 100644 --- a/src/reducers/validate/validators.js +++ b/src/reducers/validate/validators.js @@ -75,6 +75,7 @@ function validateFilterTree (node, parent, set, duplicates, hasFilterDescription * Validate domain schema */ export function validateDomain (domain, features) { + console.info(domain) const sanitizedDomain = { events: [], categories: [], From 1d1a9971abc2c2bd7cbbeb0f9f5a3307069fd427 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 26 Aug 2020 09:38:41 -0700 Subject: [PATCH 02/25] Modified validators file to match new associations schema and necessary semantic and sanitization changes --- src/reducers/validate/associationsSchema.js | 10 +++++++ src/reducers/validate/narrativeSchema.js | 9 ------- src/reducers/validate/validators.js | 30 ++++++++++----------- 3 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 src/reducers/validate/associationsSchema.js delete mode 100644 src/reducers/validate/narrativeSchema.js diff --git a/src/reducers/validate/associationsSchema.js b/src/reducers/validate/associationsSchema.js new file mode 100644 index 0000000..62251e4 --- /dev/null +++ b/src/reducers/validate/associationsSchema.js @@ -0,0 +1,10 @@ +import Joi from 'joi' + +const associationsSchema = Joi.object().keys({ + id: Joi.string().allow('').required(), + desc: Joi.string().allow(''), + mode: Joi.string().allow('').required(), + filter_paths: Joi.array(), +}) + +export default associationsSchema \ No newline at end of file diff --git a/src/reducers/validate/narrativeSchema.js b/src/reducers/validate/narrativeSchema.js deleted file mode 100644 index 2f4c8ff..0000000 --- a/src/reducers/validate/narrativeSchema.js +++ /dev/null @@ -1,9 +0,0 @@ -import Joi from 'joi' - -const narrativeSchema = Joi.object().keys({ - id: Joi.string().required(), - description: Joi.string().allow('').required(), - label: Joi.string().required() -}) - -export default narrativeSchema diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js index 2fcb7be..8486ea3 100644 --- a/src/reducers/validate/validators.js +++ b/src/reducers/validate/validators.js @@ -3,7 +3,7 @@ import Joi from 'joi' import createEventSchema from './eventSchema' import categorySchema from './categorySchema' import siteSchema from './siteSchema' -import narrativeSchema from './narrativeSchema' +import associationsSchema from './associationsSchema' import sourceSchema from './sourceSchema' import shapeSchema from './shapeSchema' @@ -27,7 +27,7 @@ function isValidDate (d) { /* * Traverse a filter tree and check its duplicates. Also recompose as -* description if `features.USE_FILTER_DESCRIPTIONS` is true. +* description if `features.USE_ASSOCIATION_DESCRIPTIONS` is true. */ function validateFilterTree (node, parent, set, duplicates, hasFilterDescriptions) { if (hasFilterDescriptions) { @@ -75,14 +75,12 @@ function validateFilterTree (node, parent, set, duplicates, hasFilterDescription * Validate domain schema */ export function validateDomain (domain, features) { - console.info(domain) const sanitizedDomain = { events: [], categories: [], sites: [], - narratives: [], + associations: [], sources: {}, - filters: {}, shapes: [], notifications: domain ? domain.notifications : null } @@ -95,7 +93,7 @@ export function validateDomain (domain, features) { events: [], categories: [], sites: [], - narratives: [], + associations: [], sources: [], shapes: [] } @@ -150,7 +148,7 @@ export function validateDomain (domain, features) { validateArray(domain.events, 'events', eventSchema) validateArray(domain.categories, 'categories', categorySchema) validateArray(domain.sites, 'sites', siteSchema) - validateArray(domain.narratives, 'narratives', narrativeSchema) + validateArray(domain.associations, 'associations', associationsSchema) validateObject(domain.sources, 'sources', sourceSchema) validateObject(domain.shapes, 'shapes', shapeSchema) @@ -163,20 +161,20 @@ export function validateDomain (domain, features) { }) ) - // Validate uniqueness of filters - const filterSet = new Set([]) - const duplicateFilters = [] - validateFilterTree(domain.filters, {}, filterSet, duplicateFilters, features.USE_FILTER_DESCRIPTIONS) + // Validate uniqueness of associations + const associationSet = new Set([]) + const duplicateAssociations = [] + validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS) - // Duplicated filters - if (duplicateFilters.length > 0) { + // Duplicated associations + if (duplicateAssociations.length > 0) { sanitizedDomain.notifications.push({ - message: `Filters are required to be unique. Ignoring duplicates for now.`, - items: duplicateFilters, + message: `Associations are required to be unique. Ignoring duplicates for now.`, + items: duplicateAssociations, type: 'error' }) } - sanitizedDomain.filters = domain.filters + sanitizedDomain.associations = domain.associations // append events with datetime and sort sanitizedDomain.events = sanitizedDomain.events.filter((event, idx) => { From fe9a5302fa091ca2a4b7d993ab09992b60bccb68 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 26 Aug 2020 22:06:13 -0700 Subject: [PATCH 03/25] Finding association duplicates; sanitizing appropriately; beginning to edit narrativise function in UI --- example.config.js | 1 + src/common/utilities.js | 22 +++---- src/components/Layout.js | 3 +- src/reducers/validate/validators.js | 92 +++++++++++++++++------------ src/store/initial.js | 10 ++-- 5 files changed, 72 insertions(+), 56 deletions(-) diff --git a/example.config.js b/example.config.js index ac443c1..8b0e1ed 100644 --- a/example.config.js +++ b/example.config.js @@ -26,6 +26,7 @@ module.exports = { USE_CATEGORIES: true, CATEGORIES_AS_FILTERS: true, USE_ASSOCIATIONS: true, + USE_ASSOCIATION_DESCRIPTIONS: true, USE_SOURCES: true, USE_COVER: true, USE_SEARCH: false, diff --git a/src/common/utilities.js b/src/common/utilities.js index 2dd4339..acc2f03 100644 --- a/src/common/utilities.js +++ b/src/common/utilities.js @@ -199,18 +199,18 @@ export function binarySearch (ar, el, compareFn) { return -m - 1 } -export const isFilterLeaf = node => (Object.keys(node.children).length === 0) -export const isFilterDuplicate = (node, set) => { return (set.has(node.key)) } +// export const isFilterLeaf = node => (Object.keys(node.children).length === 0) +// export const isFilterDuplicate = (node, set) => { return (set.has(node.key)) } -export function findDescriptionInFilterTree (key, node) { - if (node.key === key) return node.description - if (isFilterLeaf(node)) return false - const descs = Object.keys(node.children) - .map(c => findDescriptionInFilterTree(key, node.children[c])) - .filter(v => !!v) - if (descs.length !== 1) return false - return descs[0] -} +// export function findDescriptionInFilterTree (key, node) { +// if (node.key === key) return node.description +// if (isFilterLeaf(node)) return false +// const descs = Object.keys(node.children) +// .map(c => findDescriptionInFilterTree(key, node.children[c])) +// .filter(v => !!v) +// if (descs.length !== 1) return false +// return descs[0] +// } export function makeNiceDate (datetime) { if (datetime === null) return null diff --git a/src/components/Layout.js b/src/components/Layout.js index e6c794b..2edd211 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -40,7 +40,8 @@ class Dashboard extends React.Component { componentDidMount () { if (!this.props.app.isMobile) { this.props.actions.fetchDomain() - .then(domain => this.props.actions.updateDomain({ + .then(domain => + this.props.actions.updateDomain({ domain, features: this.props.features })) diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js index 8486ea3..4cd1911 100644 --- a/src/reducers/validate/validators.js +++ b/src/reducers/validate/validators.js @@ -29,46 +29,62 @@ function isValidDate (d) { * Traverse a filter tree and check its duplicates. Also recompose as * description if `features.USE_ASSOCIATION_DESCRIPTIONS` is true. */ -function validateFilterTree (node, parent, set, duplicates, hasFilterDescriptions) { - if (hasFilterDescriptions) { - if (node.key === '_root') { - node.isDescription = true // setting first set of nodes to values - } else if (!parent.isDescription) { - node.isDescription = true - } else { - node.isDescription = false - } +// function validateFilterTree (node, parent, set, duplicates, hasAssociationDescriptions) { +// if (hasAssociationDescriptions) { +// if (node.key === '_root') { +// node.isDescription = true // setting first set of nodes to values +// } else if (!parent.isDescription) { +// node.isDescription = true +// } else { +// node.isDescription = false +// } - if (node.isDescription && node.key !== 'root') { - parent.description = node.key - parent.children = node.children - delete parent.isDescription - } - if (isFilterLeaf(node)) { - delete parent.isDescription - } - } +// if (node.isDescription && node.key !== 'root') { +// parent.description = node.key +// parent.children = node.children +// delete parent.isDescription +// } +// if (isFilterLeaf(node)) { +// delete parent.isDescription +// } +// } - if (typeof (node) !== 'object' || typeof (node.children) !== 'object') { - return - } - // If it's a leaf, check that it's not duplicate - if (isFilterLeaf(node)) { - if (isFilterDuplicate(node, set)) { +// if (typeof (node) !== 'object' || typeof (node.children) !== 'object') { +// return +// } +// // If it's a leaf, check that it's not duplicate +// if (isFilterLeaf(node)) { +// if (isFilterDuplicate(node, set)) { +// duplicates.push({ +// id: node.key, +// error: makeError('Filters', node.key, 'filter was found more than once in hierarchy. Ignoring duplicate.') +// }) +// delete parent.children[node.key] +// } else { +// set.add(node.key) +// } +// } else { +// // If it's not a leaf, simply keep going +// Object.values(node.children).forEach((childNode) => { +// validateFilterTree(childNode, node, set, duplicates, hasAssociationDescriptions) +// }) +// } +// } + +function findDuplicateAssociations (associations) { + const seenSet = new Set([]) + const duplicates = [] + associations.forEach(item => { + if (seenSet.has(item.id)) { duplicates.push({ - id: node.key, - error: makeError('Filters', node.key, 'filter was found more than once in hierarchy. Ignoring duplicate.') + id: item.id, + error: makeError('Association', item.id, 'association was found more than once. Ignoring duplicate.') }) - delete parent.children[node.key] } else { - set.add(node.key) + seenSet.add(item.id) } - } else { - // If it's not a leaf, simply keep going - Object.values(node.children).forEach((childNode) => { - validateFilterTree(childNode, node, set, duplicates, hasFilterDescriptions) - }) - } + }) + return duplicates } /* @@ -162,10 +178,10 @@ export function validateDomain (domain, features) { ) // Validate uniqueness of associations - const associationSet = new Set([]) - const duplicateAssociations = [] - validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS) - + // const associationSet = new Set([]) + // const duplicateAssociations = [] + // validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS) + const duplicateAssociations = findDuplicateAssociations(domain.associations) // Duplicated associations if (duplicateAssociations.length > 0) { sanitizedDomain.notifications.push({ diff --git a/src/store/initial.js b/src/store/initial.js index 0b6f8e0..8b6d345 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -6,17 +6,16 @@ const initial = { * The Domain or 'domain' of this state refers to the tree of data * available for render and display. * Selections and filters in the 'app' subtree will operate the domain - * in mapStateToProps of the Dashboard, and deterimne which items + * in mapStateToProps of the Dashboard, and determine which items * in the domain will get rendered by React */ domain: { events: [], - narratives: [], locations: [], categories: [], + associations: [], sources: {}, sites: [], - filters: {}, notifications: [] }, @@ -24,7 +23,7 @@ const initial = { * The 'app' subtree of this state determines the data and information to be * displayed. * It may refer to those the user interacts with, by selecting, - * fitlering and so on, which ultimately operate on the data to be displayed. + * filtering and so on, which ultimately operate on the data to be displayed. * Additionally, some of the 'app' flags are determined by the config file * or by the characteristics of the client, browser, etc. */ @@ -137,12 +136,11 @@ const initial = { features: { USE_COVER: false, - USE_FILTERS: false, + USE_ASSOCIATIONS: false, USE_SEARCH: false, USE_SITES: false, USE_SOURCES: false, USE_SHAPES: false, - USE_NARRATIVES: false, GRAPH_NONLOCATED: false, HIGHLIGHT_GROUPS: false } From 2aaf7c09ff496bb0474af0cd03c9b61b51ef40e9 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Thu, 27 Aug 2020 08:42:47 -0700 Subject: [PATCH 04/25] Filter render broken; moved narrative and narrativeState to app.filters and restructured appropriately with narrativeIdx selector --- docs/configuration.md | 2 +- src/actions/index.js | 1 - src/components/Layout.js | 42 +++++++++++---------- src/components/Map.jsx | 4 +- src/components/Timeline.jsx | 4 +- src/components/Toolbar/Layout.js | 8 ++-- src/reducers/app.js | 17 +++++---- src/reducers/validate/associationsSchema.js | 4 +- src/selectors/index.js | 30 ++++++++++++--- src/store/initial.js | 7 +--- 10 files changed, 70 insertions(+), 49 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 561f6f0..db6b268 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -18,7 +18,7 @@ The URLs for these endpoints, as well as other configurable settings in your tim | SITES_EXT | Endpoint for sites, concatenated with SERVER_ROOT | String | Yes | | MAP_ANCHOR | Geographic coordinates for original map anchor | Array of numbers | No | | MAPBOX_TOKEN | Access token for Mapbox satellite imagery | String | No | -| features.USE_FILTERS | Enable / Disable filters | boolean | No | +| features.USE_ASSOCIATIONS | Enable / Disable filters | boolean | No | | features.USE_SEARCH | Enable / Disable search | boolean | No | | features.USE_SITES | Enable / Disable sites | boolean | No | diff --git a/src/actions/index.js b/src/actions/index.js index f1541bc..7850dc7 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -60,7 +60,6 @@ export function fetchDomain () { } } - let sourcesPromise = Promise.resolve([]) if (features.USE_SOURCES) { if (!SOURCES_URL) { diff --git a/src/components/Layout.js b/src/components/Layout.js index 2edd211..4e493f3 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -4,6 +4,7 @@ import React from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as actions from '../actions' +import * as selectors from '../selectors' import MediaOverlay from './Overlay/Media' import LoadingOverlay from './Overlay/Loading' @@ -42,9 +43,9 @@ class Dashboard extends React.Component { this.props.actions.fetchDomain() .then(domain => this.props.actions.updateDomain({ - domain, - features: this.props.features - })) + domain, + features: this.props.features + })) } // NOTE: hack to get the timeline to always show. Not entirely sure why // this is necessary. @@ -134,7 +135,7 @@ class Dashboard extends React.Component { setNarrativeFromFilters (withSteps) { const { app, domain } = this.props - let activeFilters = app.filters.filters + let activeFilters = app.associations.filters if (activeFilters.length === 0) { alert('No filters selected, cant narrativise') @@ -182,8 +183,8 @@ class Dashboard extends React.Component { if (typeof idx !== 'number') { let e = idx[0] || idx - if (this.props.app.narrative) { - const { steps } = this.props.app.narrative + if (this.props.app.associations.narrative) { + const { steps } = this.props.app.associations.narrative // choose the first event at a given location const locationEventId = e.id const narrativeIdxObj = steps.find(s => s.id === locationEventId) @@ -213,14 +214,14 @@ class Dashboard extends React.Component { if (narrative === null) { this.handleSelect(events[idx - 1], 0) } else { - this.selectNarrativeStep(this.props.app.narrativeState.current - 1) + this.selectNarrativeStep(this.props.narrativeIdx - 1) } } const next = idx => { if (narrative === null) { this.handleSelect(events[idx + 1], 0) } else { - this.selectNarrativeStep(this.props.app.narrativeState.current + 1) + this.selectNarrativeStep(this.props.narrativeIdx + 1) } } if (selected.length > 0) { @@ -266,7 +267,7 @@ class Dashboard extends React.Component { return (
actions.toggleFilter('filters', filter), @@ -279,13 +280,13 @@ class Dashboard extends React.Component { methods={{ onSelectNarrative: this.setNarrative, getCategoryColor: this.getCategoryColor, - onSelect: app.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 1) + onSelect: app.associations.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 1) }} /> this.handleSelect(ev, 0), + onSelect: app.associations.narrative ? this.selectNarrativeStep : ev => this.handleSelect(ev, 0), onUpdateTimerange: actions.updateTimeRange, getCategoryColor: this.getCategoryColor }} @@ -293,25 +294,25 @@ class Dashboard extends React.Component { actions.updateSelected([])} getNarrativeLinks={event => this.getNarrativeLinks(event)} getCategoryColor={this.getCategoryColor} /> 0} + showing={features.FILTERS_AS_NARRATIVES && !app.associations.narrative && app.associations.filters.length > 0} timelineDims={app.timeline.dimensions} onClickHandler={this.setNarrativeFromFilters} /> this.selectNarrativeStep(this.props.app.narrativeState.current + 1), - onPrev: () => this.selectNarrativeStep(this.props.app.narrativeState.current - 1), + onNext: () => this.selectNarrativeStep(this.props.narrativeIdx + 1), + onPrev: () => this.selectNarrativeStep(this.props.narrativeIdx - 1), onSelectNarrative: this.setNarrative }} /> @@ -360,6 +361,9 @@ function mapDispatchToProps (dispatch) { } export default connect( - state => state, + state => ({ + ...state, + narrativeIdx: selectors.selectNarrativeIdx(state) + }), mapDispatchToProps )(Dashboard) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index b40baa5..4022f88 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -275,11 +275,11 @@ function mapStateToProps (state) { shapes: selectors.selectShapes(state) }, app: { - views: state.app.filters.views, + views: state.app.associations.views, selected: selectors.selectSelected(state), highlighted: state.app.highlighted, map: state.app.map, - narrative: state.app.narrative, + narrative: state.app.associations.narrative, flags: { isShowingSites: state.app.flags.isShowingSites } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index bd34538..11b9d87 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -398,7 +398,7 @@ class Timeline extends React.Component { function mapStateToProps (state) { return { dimensions: selectors.selectDimensions(state), - isNarrative: !!state.app.narrative, + isNarrative: !!state.app.associations.narrative, domain: { events: selectors.selectStackedEvents(state), projects: selectors.selectProjects(state), @@ -409,7 +409,7 @@ function mapStateToProps (state) { selected: state.app.selected, language: state.app.language, timeline: state.app.timeline, - narrative: state.app.narrative + narrative: state.app.associations.narrative }, ui: { dom: state.ui.dom, diff --git a/src/components/Toolbar/Layout.js b/src/components/Toolbar/Layout.js index feaa8e0..ce551f1 100644 --- a/src/components/Toolbar/Layout.js +++ b/src/components/Toolbar/Layout.js @@ -121,7 +121,7 @@ class Toolbar extends React.Component { {features.USE_NARRATIVES ? this.renderToolbarNarrativePanel() : null} {features.CATEGORIES_AS_FILTERS ? this.renderToolbarCategoriesPanel() : null} - {features.USE_FILTERS ? this.renderToolbarFilterPanel() : null} + {features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null}
) @@ -163,7 +163,7 @@ class Toolbar extends React.Component {
{features.USE_NARRATIVES ? this.renderToolbarTab(narrativesIdx, narrativesLabel, 'timeline') : null} {features.CATEGORIES_AS_FILTERS ? this.renderToolbarTab(categoriesIdx, categoriesLabel, 'widgets') : null} - {features.USE_FILTERS ? this.renderToolbarTab(filtersIdx, filtersLabel, 'filter_list') : null} + {features.USE_ASSOCIATIONS ? this.renderToolbarTab(filtersIdx, filtersLabel, 'filter_list') : null}
state.domain.events export const getCategories = state => state.domain.categories export const getNarratives = state => state.domain.narratives -export const getActiveNarrative = state => state.app.narrative -export const getActiveStep = state => state.app.narrativeState.current +export const getActiveNarrative = state => state.app.associations.narrative export const getSelected = state => state.app.selected export const getSites = state => state.domain.sites export const getSources = state => state.domain.sources export const getShapes = state => state.domain.shapes export const getNotifications = state => state.domain.notifications export const getFilterTree = state => state.domain.filters -export const getActiveFilters = state => state.app.filters.filters -export const getActiveCategories = state => state.app.filters.categories +export const getActiveFilters = state => state.app.associations.filters +export const getActiveCategories = state => state.app.associations.categories export const getTimeRange = state => state.app.timeline.range export const getTimelineDimensions = state => state.app.timeline.dimensions -export const selectNarrative = state => state.app.narrative +export const selectNarrative = state => state.app.associations.narrative export const getFeatures = state => state.features export const getEventRadius = state => state.ui.eventRadius @@ -113,11 +112,30 @@ export const selectNarratives = createSelector( return narrativesMeta.map(n => narratives[n.id]).filter(d => d) }) +/** We iterate through narrative.steps and check the idx there against the selected array and we return the idx */ +export const selectNarrativeIdx = createSelector( + [getSelected, getActiveNarrative], + (selected, narrative) => { + // Only one event selected in narrative mode + if (narrative === null) return -1 + + const selectedEvent = selected[0] + let selectedIdx + + narrative.steps.forEach((step, idx) => { + if (selectedEvent.id === step.id) { + selectedIdx = idx + } + }) + return selectedIdx + } +) + /** 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], + [getActiveNarrative, selectNarrativeIdx], (narrative, current) => narrative ? { ...narrative, current } : null diff --git a/src/store/initial.js b/src/store/initial.js index 8b6d345..bb130ed 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -34,12 +34,9 @@ const initial = { highlighted: null, selected: [], source: null, - narrative: null, - narrativeState: { - current: null - }, - filters: { + associations: { filters: [], + narrative: null, categories: [], views: { events: true, From 6492be18d9b8dba3fc3c99a6c27c4e41261f9690 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 1 Sep 2020 09:33:05 -0700 Subject: [PATCH 05/25] Refactored filter list for display; converting filter paths to node, child objects that are toggleable --- src/common/constants.js | 2 + src/components/Toolbar/FilterListPanel.js | 62 ++++++++++++++--------- src/components/Toolbar/Layout.js | 2 +- src/reducers/app.js | 8 +-- src/selectors/index.js | 3 +- 5 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 src/common/constants.js diff --git a/src/common/constants.js b/src/common/constants.js new file mode 100644 index 0000000..4cd536e --- /dev/null +++ b/src/common/constants.js @@ -0,0 +1,2 @@ +export const FILTER_MODE = 'FILTER' +export const NARRATIVE_MODE = 'NARRATIVE' \ No newline at end of file diff --git a/src/components/Toolbar/FilterListPanel.js b/src/components/Toolbar/FilterListPanel.js index 53bb31a..9c14c10 100644 --- a/src/components/Toolbar/FilterListPanel.js +++ b/src/components/Toolbar/FilterListPanel.js @@ -3,57 +3,73 @@ import Checkbox from '../presentational/Checkbox' import copy from '../../common/data/copy.json' /** recursively get an array of node keys to toggle */ -function childrenToToggle (node, activeFilters, parentOn) { - const isOn = activeFilters.includes(node.key) - if (!node.children) { - return [node.key] +function childrenToToggle (filter, activeFilters, parentOn) { + const [key, children] = filter + const isOn = activeFilters.includes(key) + if (children === {}) { + return [key] } - const childKeys = Object.values(node.children) - .flatMap(n => childrenToToggle(n, activeFilters, isOn)) + const childKeys = Object.entries(children) + .flatMap(filter => childrenToToggle(filter, activeFilters, isOn)) // NB: if turning a parent off, don't toggle off children on. // likewise if turning a parent on, don't toggle on children off if (!((!parentOn && isOn) || (parentOn && !isOn))) { - childKeys.push(node.key) + childKeys.push(key) } return childKeys } +function aggregatePaths (filters) { + const aggregated = {} + + filters.forEach(item => { + let currentDepth = aggregated + + item.filter_paths.forEach(path => { + if (!(path in aggregated)) { + currentDepth[path] = {} + } + currentDepth = currentDepth[path] + }) + }) + + return aggregated +} + function FilterListPanel ({ filters, activeFilters, onSelectFilter, language }) { - function createNodeComponent (node, depth) { - const matchingKeys = childrenToToggle(node, activeFilters, activeFilters.includes(node.key)) - const children = Object.values(node.children) + function createNodeComponent (filter, depth) { + const [key, children] = filter + const matchingKeys = childrenToToggle(filter, activeFilters, activeFilters.includes(key)) + return (
  • - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} onSelectFilter(matchingKeys)} /> - {children.length > 0 - ? children.map(filter => createNodeComponent(filter, depth + 1)) + {Object.keys(children).length > 0 + ? Object.entries(children).map(filter => createNodeComponent(filter, depth + 1)) : null}
  • ) } - function renderTree (children) { + function renderTree (filters) { + const aggregatedFilterPaths = aggregatePaths(filters) + console.info(aggregatedFilterPaths) return (
    - {Object.values(children).map(filter => createNodeComponent(filter, 1))} + {Object.entries(aggregatedFilterPaths).map(filter => createNodeComponent(filter, 1))}
    ) } @@ -62,7 +78,7 @@ function FilterListPanel ({

    {copy[language].toolbar.filters}

    {copy[language].toolbar.explore_by_filter__description}

    - {renderTree(filters.children)} + {renderTree(filters)}
    ) } diff --git a/src/components/Toolbar/Layout.js b/src/components/Toolbar/Layout.js index ce551f1..a2df306 100644 --- a/src/components/Toolbar/Layout.js +++ b/src/components/Toolbar/Layout.js @@ -197,7 +197,7 @@ class Toolbar extends React.Component { function mapStateToProps (state) { return { - filters: selectors.getFilterTree(state), + filters: selectors.getFilters(state), categories: selectors.getCategories(state), narratives: selectors.selectNarratives(state), language: state.app.language, diff --git a/src/reducers/app.js b/src/reducers/app.js index 7b58372..4039f8a 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -114,7 +114,7 @@ function toggleFilter (appState, action) { action.value = [action.value] } - let newFilters = appState.filters[action.filter].slice(0) + let newFilters = appState.associations.filters.slice(0) action.value.forEach(vl => { if (newFilters.includes(vl)) { newFilters = newFilters.filter(s => s !== vl) @@ -125,9 +125,9 @@ function toggleFilter (appState, action) { return { ...appState, - filters: { - ...appState.filters, - [action.filter]: newFilters + associations: { + ...appState.associations, + filters: newFilters, } } } diff --git a/src/selectors/index.js b/src/selectors/index.js index c0806d5..d328da1 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -1,6 +1,7 @@ import { createSelector } from 'reselect' import { insetSourceFrom, dateMin, dateMax } from '../common/utilities' import { isTimeRangedIn } from './helpers' +import { FILTER_MODE } from '../common/constants' // Input selectors export const getEvents = state => state.domain.events @@ -11,8 +12,8 @@ export const getSelected = state => state.app.selected export const getSites = state => state.domain.sites export const getSources = state => state.domain.sources export const getShapes = state => state.domain.shapes +export const getFilters = state => state.domain.associations.filter(item => item.mode === FILTER_MODE) export const getNotifications = state => state.domain.notifications -export const getFilterTree = state => state.domain.filters export const getActiveFilters = state => state.app.associations.filters export const getActiveCategories = state => state.app.associations.categories export const getTimeRange = state => state.app.timeline.range From bd4f61e7edc900dd7cb1b4f1d11b0d70258c3742 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Mon, 7 Sep 2020 16:10:09 -0700 Subject: [PATCH 06/25] Working narrative feature minus getNarrativeLinks (need further clarification); working on modifying narrativise filters feature --- example.config.js | 1 + src/components/Layout.js | 8 +++++--- src/components/Toolbar/FilterListPanel.js | 1 - src/components/Toolbar/Layout.js | 4 ++-- src/reducers/app.js | 3 +-- src/selectors/index.js | 16 +++++++++------- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/example.config.js b/example.config.js index 8b0e1ed..3467996 100644 --- a/example.config.js +++ b/example.config.js @@ -24,6 +24,7 @@ module.exports = { }, features: { USE_CATEGORIES: true, + USE_NARRATIVES: true, CATEGORIES_AS_FILTERS: true, USE_ASSOCIATIONS: true, USE_ASSOCIATION_DESCRIPTIONS: true, diff --git a/src/components/Layout.js b/src/components/Layout.js index 4e493f3..ded3ed4 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -119,8 +119,9 @@ class Dashboard extends React.Component { } } + // Broken for time being; need clarification on function getNarrativeLinks (event) { - const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative) + const narrative = this.props.narratives.find(nv => nv.id === event.narratives[0]) if (narrative) return narrative.byId[event.id] return null } @@ -196,7 +197,7 @@ class Dashboard extends React.Component { } } - const { narrative } = this.props.app + const { narrative } = this.props.app.associations if (narrative === null) return if (idx < narrative.steps.length && idx >= 0) { @@ -363,7 +364,8 @@ function mapDispatchToProps (dispatch) { export default connect( state => ({ ...state, - narrativeIdx: selectors.selectNarrativeIdx(state) + narrativeIdx: selectors.selectNarrativeIdx(state), + narratives: selectors.selectNarratives(state) }), mapDispatchToProps )(Dashboard) diff --git a/src/components/Toolbar/FilterListPanel.js b/src/components/Toolbar/FilterListPanel.js index 9c14c10..c4f4d22 100644 --- a/src/components/Toolbar/FilterListPanel.js +++ b/src/components/Toolbar/FilterListPanel.js @@ -66,7 +66,6 @@ function FilterListPanel ({ function renderTree (filters) { const aggregatedFilterPaths = aggregatePaths(filters) - console.info(aggregatedFilterPaths) return (
    {Object.entries(aggregatedFilterPaths).map(filter => createNodeComponent(filter, 1))} diff --git a/src/components/Toolbar/Layout.js b/src/components/Toolbar/Layout.js index a2df306..dc61ce7 100644 --- a/src/components/Toolbar/Layout.js +++ b/src/components/Toolbar/Layout.js @@ -62,8 +62,8 @@ class Toolbar extends React.Component { return (
    ) diff --git a/src/reducers/app.js b/src/reducers/app.js index 4039f8a..6498eb1 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -82,11 +82,10 @@ function updateNarrative (appState, action) { minTime = minTime - Math.abs((maxTime - minTime) / 10) maxTime = maxTime + Math.abs((maxTime - minTime) / 10) } - return { ...appState, associations: { - ...appState.filters, + ...appState.associations, narrative: action.narrative }, map: { diff --git a/src/selectors/index.js b/src/selectors/index.js index d328da1..d5f319c 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -1,12 +1,12 @@ import { createSelector } from 'reselect' import { insetSourceFrom, dateMin, dateMax } from '../common/utilities' import { isTimeRangedIn } from './helpers' -import { FILTER_MODE } from '../common/constants' +import { FILTER_MODE, NARRATIVE_MODE } from '../common/constants' // Input selectors export const getEvents = state => state.domain.events export const getCategories = state => state.domain.categories -export const getNarratives = state => state.domain.narratives +export const getNarratives = state => state.domain.associations.filter(item => item.mode === NARRATIVE_MODE) export const getActiveNarrative = state => state.app.associations.narrative export const getSelected = state => state.app.selected export const getSites = state => state.domain.sites @@ -87,27 +87,29 @@ export const selectNarratives = createSelector( evt.narratives.forEach(narrative => { // initialise if (!narratives[narrative]) { narratives[narrative] = narrativeSkeleton(narrative) } - // add evt to steps // 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((a, b) => a.datetime - b.datetime) - if (narrativesMeta.find(n => n.id === key)) { + const existingAssociatedNarrative = narrativesMeta.find(n => n.id === key) + + if (existingAssociatedNarrative) { narratives[key] = { - ...narrativesMeta.find(n => n.id === key), + ...existingAssociatedNarrative, ...narratives[key] } + } else { + // Associations dont contain this narrative + delete narratives[key] } }) - // Return narratives in original order // + filter those that are undefined return narrativesMeta.map(n => narratives[n.id]).filter(d => d) From 4c5bafcc05e2b2915817de783abc2531e8c7613b Mon Sep 17 00:00:00 2001 From: efarooqui Date: Mon, 7 Sep 2020 16:26:10 -0700 Subject: [PATCH 07/25] Need clarification on USE_ASSOCIATION_DESCRIPTIONS for narrativise feature --- example.config.js | 1 + src/components/Layout.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example.config.js b/example.config.js index 3467996..168e4ec 100644 --- a/example.config.js +++ b/example.config.js @@ -25,6 +25,7 @@ module.exports = { features: { USE_CATEGORIES: true, USE_NARRATIVES: true, + FILTERS_AS_NARRATIVES: false, CATEGORIES_AS_FILTERS: true, USE_ASSOCIATIONS: true, USE_ASSOCIATION_DESCRIPTIONS: true, diff --git a/src/components/Layout.js b/src/components/Layout.js index ded3ed4..a6bff0d 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -20,7 +20,7 @@ import StaticPage from './StaticPage' import TemplateCover from './TemplateCover' import colors from '../common/global' -import { binarySearch, insetSourceFrom, findDescriptionInFilterTree } from '../common/utilities' +import { binarySearch, insetSourceFrom } from '../common/utilities' import { isMobile } from 'react-device-detect' class Dashboard extends React.Component { @@ -143,7 +143,7 @@ class Dashboard extends React.Component { return } - if (this.props.features.USE_FILTER_DESCRIPTIONS) { + if (this.props.features.USE_ASSOCIATION_DESCRIPTIONS) { activeFilters = activeFilters.reduce((acc, vl) => { acc.push({ name: vl, From c264071cdb298ab054278dc702d631c62e01badd Mon Sep 17 00:00:00 2001 From: efarooqui Date: Mon, 7 Sep 2020 16:28:19 -0700 Subject: [PATCH 08/25] Setting USE_ASSOC_DESC to fals --- example.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example.config.js b/example.config.js index 168e4ec..30ca530 100644 --- a/example.config.js +++ b/example.config.js @@ -25,10 +25,10 @@ module.exports = { features: { USE_CATEGORIES: true, USE_NARRATIVES: true, - FILTERS_AS_NARRATIVES: false, + FILTERS_AS_NARRATIVES: true, CATEGORIES_AS_FILTERS: true, USE_ASSOCIATIONS: true, - USE_ASSOCIATION_DESCRIPTIONS: true, + USE_ASSOCIATION_DESCRIPTIONS: false, USE_SOURCES: true, USE_COVER: true, USE_SEARCH: false, From a1f7cf69d4c68b759c5801af7350dd8acfc9906f Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 9 Sep 2020 07:43:33 -0700 Subject: [PATCH 09/25] Beginning to remove extraneous components and logic --- src/components/Card.jsx | 6 +++--- src/components/CardStack.jsx | 2 +- src/components/Layout.js | 25 +++++++++++++------------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 8fc9f56..9bca2fc 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -108,7 +108,7 @@ class Card extends React.Component { } renderNarrative () { - const links = this.props.getNarrativeLinks(this.props.event) + // const links = this.props.getNarrativeLinks(this.props.event) if (links !== null) { return ( @@ -148,9 +148,9 @@ class Card extends React.Component { renderExtra () { return (
    - {this.renderFilters()} + {/* {this.renderFilters()} */} {this.renderSources()} - {this.renderNarrative()} + {/* {this.renderNarrative()} */}
    ) } diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index 9893190..c4dd233 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -66,7 +66,7 @@ class CardStack extends React.Component { language={this.props.language} isLoading={this.props.isLoading} isSelected={selections[idx]} - getNarrativeLinks={this.props.getNarrativeLinks} + // getNarrativeLinks={this.props.getNarrativeLinks} getCategoryGroup={this.props.getCategoryGroup} getCategoryColor={this.props.getCategoryColor} getCategoryLabel={this.props.getCategoryLabel} diff --git a/src/components/Layout.js b/src/components/Layout.js index a6bff0d..4e290ae 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -143,20 +143,21 @@ class Dashboard extends React.Component { return } - if (this.props.features.USE_ASSOCIATION_DESCRIPTIONS) { - activeFilters = activeFilters.reduce((acc, vl) => { - acc.push({ - name: vl, - description: findDescriptionInFilterTree(vl, domain.filters) - }) - return acc - }, []) - } else { - activeFilters = activeFilters.map(f => ({ name: f })) - } + // if (this.props.features.USE_ASSOCIATION_DESCRIPTIONS) { + // activeFilters = activeFilters.reduce((acc, vl) => { + // acc.push({ + // name: vl, + // description: findDescriptionInFilterTree(vl, domain.filters) + // }) + // return acc + // }, []) + // } else { + // activeFilters = activeFilters.map(f => ({ name: f })) + // } + activeFilters = activeFilters.map(f => ({ name: f })) const evs = domain.events.filter(ev => { - let hasOne = false + let hasOne = false // add event if it has at least one matching filter for (let i = 0; i < activeFilters.length; i++) { if (ev.filters.includes(activeFilters[i].name)) { From 10e905afebfc100e2ca458240bf7dd7d0668e5b7 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 9 Sep 2020 20:38:11 -0700 Subject: [PATCH 10/25] Deprecated usage of env constants USE_ASSOCIATION_DESCRIPTIONS, USE_NARRATIVESand FILTERS_AS_NARRATIVES to instead check for existence of narratives; deprecated usage of unused components such as CardFilters, CardNarrative, and NarrativeLink --- example.config.js | 3 -- src/components/Card.jsx | 29 --------------- src/components/Layout.js | 2 +- src/components/Toolbar/Layout.js | 15 ++++---- src/components/presentational/Card/Filters.js | 36 ------------------- .../presentational/Card/Narrative.js | 15 -------- .../presentational/Card/NarrativeLink.js | 17 --------- .../presentational/Map/Narratives.js | 6 ++-- src/selectors/index.js | 2 +- 9 files changed, 14 insertions(+), 111 deletions(-) delete mode 100644 src/components/presentational/Card/Filters.js delete mode 100644 src/components/presentational/Card/Narrative.js delete mode 100644 src/components/presentational/Card/NarrativeLink.js diff --git a/example.config.js b/example.config.js index 30ca530..ac443c1 100644 --- a/example.config.js +++ b/example.config.js @@ -24,11 +24,8 @@ module.exports = { }, features: { USE_CATEGORIES: true, - USE_NARRATIVES: true, - FILTERS_AS_NARRATIVES: true, CATEGORIES_AS_FILTERS: true, USE_ASSOCIATIONS: true, - USE_ASSOCIATION_DESCRIPTIONS: false, USE_SOURCES: true, USE_COVER: true, USE_SEARCH: false, diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 9bca2fc..4079f60 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -5,10 +5,8 @@ import CardCustomField from './presentational/Card/CustomField' import CardTime from './presentational/Card/Time' import CardLocation from './presentational/Card/Location' import CardCaret from './presentational/Card/Caret' -import CardFilters from './presentational/Card/Filters' import CardSummary from './presentational/Card/Summary' import CardSource from './presentational/Card/Source' -import CardNarrative from './presentational/Card/Narrative' import { makeNiceDate } from '../common/utilities' class Card extends React.Component { @@ -39,18 +37,6 @@ class Card extends React.Component { ) } - renderFilters () { - if (!this.props.filters || (this.props.filters && this.props.filters.length === 0)) { - return null - } - return ( - - ) - } - renderLocation () { return ( this.props.onSelect([event])} - makeTimelabel={(timestamp) => this.makeTimelabel(timestamp)} - next={links.next} - prev={links.prev} - /> - ) - } - } - renderCustomFields () { return this.props.features.CUSTOM_EVENT_FIELDS .map(field => { diff --git a/src/components/Layout.js b/src/components/Layout.js index 4e290ae..36304a9 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -303,7 +303,7 @@ class Dashboard extends React.Component { getCategoryColor={this.getCategoryColor} /> 0} + showing={this.props.narratives && this.props.narratives.length !== 0 && !app.associations.narrative && app.associations.filters.length > 0} timelineDims={app.timeline.dimensions} onClickHandler={this.setNarrativeFromFilters} /> diff --git a/src/components/Toolbar/Layout.js b/src/components/Toolbar/Layout.js index dc61ce7..cceab2a 100644 --- a/src/components/Toolbar/Layout.js +++ b/src/components/Toolbar/Layout.js @@ -113,13 +113,13 @@ class Toolbar extends React.Component { } renderToolbarPanels () { - const { features } = this.props + const { features, narratives } = this.props let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded' return (
    {this.renderClosePanel()} - {features.USE_NARRATIVES ? this.renderToolbarNarrativePanel() : null} + {narratives && narratives.length !== 0 ? this.renderToolbarNarrativePanel() : null} {features.CATEGORIES_AS_FILTERS ? this.renderToolbarCategoriesPanel() : null} {features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null} @@ -145,7 +145,8 @@ class Toolbar extends React.Component { } renderToolbarTabs () { - const { features } = this.props + const { features, narratives } = this.props + const narrativesExist = narratives && narratives.length !== 0 let title = copy[this.props.language].toolbar.title if (process.env.display_title) title = process.env.display_title const narrativesLabel = copy[this.props.language].toolbar.narratives_label @@ -153,15 +154,15 @@ class Toolbar extends React.Component { const categoriesLabel = 'Categories' // TODO: const narrativesIdx = 0 - const categoriesIdx = features.USE_NARRATIVES ? 1 : 0 - const filtersIdx = (features.USE_NARRATIVES && features.CATEGORIES_AS_FILTERS) ? 2 : ( - features.USE_NARRATIVES || features.CATEGORIES_AS_FILTERS ? 1 : 0 + const categoriesIdx = narrativesExist ? 1 : 0 + const filtersIdx = (narrativesExist && features.CATEGORIES_AS_FILTERS) ? 2 : ( + narrativesExist || features.CATEGORIES_AS_FILTERS ? 1 : 0 ) return (

    {title}

    - {features.USE_NARRATIVES ? this.renderToolbarTab(narrativesIdx, narrativesLabel, 'timeline') : null} + {narrativesExist ? this.renderToolbarTab(narrativesIdx, narrativesLabel, 'timeline') : null} {features.CATEGORIES_AS_FILTERS ? this.renderToolbarTab(categoriesIdx, categoriesLabel, 'widgets') : null} {features.USE_ASSOCIATIONS ? this.renderToolbarTab(filtersIdx, filtersLabel, 'filter_list') : null}
    diff --git a/src/components/presentational/Card/Filters.js b/src/components/presentational/Card/Filters.js deleted file mode 100644 index 0b90de4..0000000 --- a/src/components/presentational/Card/Filters.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' - -import copy from '../../../common/data/copy.json' - -const CardFilters = ({ filters, language }) => { - const filtersLang = copy[language].cardstack.filters - const noFiltersLang = copy[language].cardstack.nofilters - - if (filters.length > 0) { - return ( -
    -

    {filtersLang}:

    -

    - {filters.map((filter, idx) => { - return ( - - {filter.name} - {(idx < filters.length - 1) - ? ',' - : ''} - - ) - })} -

    -
    - ) - } - return ( -
    -

    {filtersLang}

    -

    {noFiltersLang}

    -
    - ) -} - -export default CardFilters diff --git a/src/components/presentational/Card/Narrative.js b/src/components/presentational/Card/Narrative.js deleted file mode 100644 index 150180f..0000000 --- a/src/components/presentational/Card/Narrative.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' - -import CardNarrativeLink from './NarrativeLink' - -const CardNarrative = (props) => ( -
    -

    Connected events

    -
    -

    -

    -
    -
    -) - -export default CardNarrative diff --git a/src/components/presentational/Card/NarrativeLink.js b/src/components/presentational/Card/NarrativeLink.js deleted file mode 100644 index b292e16..0000000 --- a/src/components/presentational/Card/NarrativeLink.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -const CardNarrativeLink = ({ event, makeTimelabel, select }) => { - if (event !== null) { - const timelabel = makeTimelabel(event.timestamp) - - return ( - select(event)}> - {`${timelabel} / ${event.location}`} - - ) - } - - return (None) -} - -export default CardNarrativeLink diff --git a/src/components/presentational/Map/Narratives.js b/src/components/presentational/Map/Narratives.js index 8e0aefa..cbae8c7 100644 --- a/src/components/presentational/Map/Narratives.js +++ b/src/components/presentational/Map/Narratives.js @@ -26,6 +26,8 @@ function MapNarratives ({ return styles[styleName] } + const narrativesExist = narratives && narratives.length !== 0 + function hasNoLocation (step) { return (step.latitude === '' || step.longitude === '') } @@ -141,7 +143,7 @@ function MapNarratives ({ let lastMarked = null - if (features.FILTERS_AS_NARRATIVES) { + if (narrativesExist) { for (let idx = 0; idx < n.steps.length; idx += 1) { const step = n.steps[idx] if (lastMarked) { @@ -174,7 +176,7 @@ function MapNarratives ({ function renderNarrative (n) { const narrativeId = `narrative-${n.id.replace(/ /g, '_')}` - const body = features.FILTERS_AS_NARRATIVES + const body = narrativesExist ? renderBetweenMarked(n) : (features.NARRATIVE_STEP_STYLES ? renderBetweenMarked(n) diff --git a/src/selectors/index.js b/src/selectors/index.js index d5f319c..c8ad30e 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -76,7 +76,7 @@ export const selectEvents = createSelector( export const selectNarratives = createSelector( [getEvents, getNarratives, getSources, getFeatures], (events, narrativesMeta, sources, features) => { - if (!features.USE_NARRATIVES) { + if (Array.isArray(narrativesMeta) && narrativesMeta.length === 0) { return [] } const narratives = {} From ed7c3551d6964835b329da5b6ec0503236803fd4 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 9 Sep 2020 20:41:57 -0700 Subject: [PATCH 11/25] Got rid of narrativeLinks in CardStack and deprecated usage of USE_ASSOCIATION_DESCRIPTIONS in embedded logic --- src/components/CardStack.jsx | 1 - src/components/Layout.js | 19 ----------- src/reducers/validate/validators.js | 50 ----------------------------- 3 files changed, 70 deletions(-) diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index c4dd233..1f6c371 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -66,7 +66,6 @@ class CardStack extends React.Component { language={this.props.language} isLoading={this.props.isLoading} isSelected={selections[idx]} - // getNarrativeLinks={this.props.getNarrativeLinks} getCategoryGroup={this.props.getCategoryGroup} getCategoryColor={this.props.getCategoryColor} getCategoryLabel={this.props.getCategoryLabel} diff --git a/src/components/Layout.js b/src/components/Layout.js index 36304a9..72cd0bf 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -119,13 +119,6 @@ class Dashboard extends React.Component { } } - // Broken for time being; need clarification on function - getNarrativeLinks (event) { - const narrative = this.props.narratives.find(nv => nv.id === event.narratives[0]) - if (narrative) return narrative.byId[event.id] - return null - } - setNarrative (narrative) { // only handleSelect if narrative is not null if (narrative) { @@ -143,17 +136,6 @@ class Dashboard extends React.Component { return } - // if (this.props.features.USE_ASSOCIATION_DESCRIPTIONS) { - // activeFilters = activeFilters.reduce((acc, vl) => { - // acc.push({ - // name: vl, - // description: findDescriptionInFilterTree(vl, domain.filters) - // }) - // return acc - // }, []) - // } else { - // activeFilters = activeFilters.map(f => ({ name: f })) - // } activeFilters = activeFilters.map(f => ({ name: f })) const evs = domain.events.filter(ev => { @@ -299,7 +281,6 @@ class Dashboard extends React.Component { onSelect={app.associations.narrative ? this.selectNarrativeStep : this.handleSelect} onHighlight={this.handleHighlight} onToggleCardstack={() => actions.updateSelected([])} - getNarrativeLinks={event => this.getNarrativeLinks(event)} getCategoryColor={this.getCategoryColor} /> { -// validateFilterTree(childNode, node, set, duplicates, hasAssociationDescriptions) -// }) -// } -// } - function findDuplicateAssociations (associations) { const seenSet = new Set([]) const duplicates = [] @@ -177,10 +131,6 @@ export function validateDomain (domain, features) { }) ) - // Validate uniqueness of associations - // const associationSet = new Set([]) - // const duplicateAssociations = [] - // validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS) const duplicateAssociations = findDuplicateAssociations(domain.associations) // Duplicated associations if (duplicateAssociations.length > 0) { From 2bc6affbf59cafb2981ad4a1fb207cd74b3d24d8 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Wed, 9 Sep 2020 21:22:22 -0700 Subject: [PATCH 12/25] Fixed up event schema to reduce filters and narratives to associations; removed extraneous comments for deprecated functions --- example.config.js | 2 +- src/common/utilities.js | 13 ------------- src/components/Card.jsx | 2 -- src/reducers/validate/eventSchema.js | 5 +++-- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/example.config.js b/example.config.js index ac443c1..9a72ab6 100644 --- a/example.config.js +++ b/example.config.js @@ -31,7 +31,7 @@ module.exports = { USE_SEARCH: false, USE_SITES: false, USE_SHAPES: false, - GRAPH_NONLOCATED: false, + GRAPH_NONLOHATED: false, HIGHLIGHT_GROUPS: false } } diff --git a/src/common/utilities.js b/src/common/utilities.js index acc2f03..f384105 100644 --- a/src/common/utilities.js +++ b/src/common/utilities.js @@ -199,19 +199,6 @@ export function binarySearch (ar, el, compareFn) { return -m - 1 } -// export const isFilterLeaf = node => (Object.keys(node.children).length === 0) -// export const isFilterDuplicate = (node, set) => { return (set.has(node.key)) } - -// export function findDescriptionInFilterTree (key, node) { -// if (node.key === key) return node.description -// if (isFilterLeaf(node)) return false -// const descs = Object.keys(node.children) -// .map(c => findDescriptionInFilterTree(key, node.children[c])) -// .filter(v => !!v) -// if (descs.length !== 1) return false -// return descs[0] -// } - export function makeNiceDate (datetime) { if (datetime === null) return null // see https://stackoverflow.com/questions/3552461/how-to-format-a-javascript-date diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 4079f60..57ad170 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -119,9 +119,7 @@ class Card extends React.Component { renderExtra () { return (
    - {/* {this.renderFilters()} */} {this.renderSources()} - {/* {this.renderNarrative()} */}
    ) } diff --git a/src/reducers/validate/eventSchema.js b/src/reducers/validate/eventSchema.js index 6b7cb64..5608b95 100644 --- a/src/reducers/validate/eventSchema.js +++ b/src/reducers/validate/eventSchema.js @@ -23,9 +23,10 @@ function createEventSchema (custom) { type: Joi.string().allow(''), category: Joi.string().allow(''), category_full: Joi.string().allow(''), - narratives: Joi.array(), + associations: Joi.array(), + // narratives: Joi.array(), sources: Joi.array(), - filters: Joi.array().allow(''), + // filters: Joi.array().allow(''), tags: Joi.array().allow(''), comments: Joi.string().allow(''), time_display: Joi.string().allow(''), From 57818dd971b291055683cb4a233d17c0571d31b5 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Thu, 10 Sep 2020 22:39:29 -0700 Subject: [PATCH 13/25] Events now only have associations; when filters and narratives are needed fromevents, interpolate from evt.associations --- src/components/Layout.js | 2 +- src/reducers/validate/eventSchema.js | 4 +--- src/reducers/validate/validators.js | 10 +++++----- src/selectors/index.js | 27 ++++++++++++++------------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/components/Layout.js b/src/components/Layout.js index 72cd0bf..feaaa80 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -142,7 +142,7 @@ class Dashboard extends React.Component { let hasOne = false // add event if it has at least one matching filter for (let i = 0; i < activeFilters.length; i++) { - if (ev.filters.includes(activeFilters[i].name)) { + if (ev.associations.includes(activeFilters[i].name)) { hasOne = true break } diff --git a/src/reducers/validate/eventSchema.js b/src/reducers/validate/eventSchema.js index 5608b95..112f07e 100644 --- a/src/reducers/validate/eventSchema.js +++ b/src/reducers/validate/eventSchema.js @@ -24,10 +24,8 @@ function createEventSchema (custom) { category: Joi.string().allow(''), category_full: Joi.string().allow(''), associations: Joi.array(), - // narratives: Joi.array(), sources: Joi.array(), - // filters: Joi.array().allow(''), - tags: Joi.array().allow(''), + // tags: Joi.array().allow(''), comments: Joi.string().allow(''), time_display: Joi.string().allow(''), // nested diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js index ba22fcd..305ceb0 100644 --- a/src/reducers/validate/validators.js +++ b/src/reducers/validate/validators.js @@ -84,11 +84,11 @@ export function validateDomain (domain, features) { function validateArray (items, domainKey, schema) { items.forEach(item => { // NB: backwards compatibility with 'tags' for 'filters' - if (domainKey === 'events') { - if (!item.filters && !!item.tags) { - item.filters = item.tags - } - } + // if (domainKey === 'events') { + // if (!item.filters && !!item.tags) { + // item.filters = item.tags + // } + // } validateArrayItem(item, domainKey, schema) }) } diff --git a/src/selectors/index.js b/src/selectors/index.js index c8ad30e..c9592ed 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -49,9 +49,9 @@ export const selectEvents = createSelector( [getEvents, getActiveFilters, getActiveCategories, getTimeRange, getFeatures], (events, activeFilters, activeCategories, timeRange, features) => { return events.reduce((acc, event) => { - const isMatchingFilter = (event.filters && - event.filters.map(filter => - activeFilters.includes(filter)) + const isMatchingFilter = (event.associations && + event.associations.map(association => + activeFilters.includes(association)) .some(s => s) ) || activeFilters.length === 0 const isActiveFilter = isMatchingFilter || activeFilters.length === 0 @@ -84,12 +84,16 @@ export const selectNarratives = createSelector( /* populate narratives dict with events */ events.forEach(evt => { - evt.narratives.forEach(narrative => { - // initialise - if (!narratives[narrative]) { narratives[narrative] = narrativeSkeleton(narrative) } - // add evt to steps - // NB: insetSourceFrom is a 'curried' function to allow with maps - narratives[narrative].steps.push(insetSourceFrom(sources)(evt)) + evt.associations.forEach(association => { + const foundNarrative = narrativesMeta.find(narr => narr.id === association) + if (!!foundNarrative) { + const { id: narrId } = foundNarrative + // initialise + if (!narratives[narrId]) { narratives[narrId] = narrativeSkeleton(narrId) } + // add evt to steps + // NB: insetSourceFrom is a 'curried' function to allow with maps + narratives[narrId].steps.push(insetSourceFrom(sources)(evt)) + } }) }) /* sort steps by time */ @@ -100,14 +104,11 @@ export const selectNarratives = createSelector( const existingAssociatedNarrative = narrativesMeta.find(n => n.id === key) - if (existingAssociatedNarrative) { + if (!!existingAssociatedNarrative) { narratives[key] = { ...existingAssociatedNarrative, ...narratives[key] } - } else { - // Associations dont contain this narrative - delete narratives[key] } }) // Return narratives in original order From c60ee2479297ec0a4d75b1a0cb03bf02c04e87f2 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Thu, 10 Sep 2020 22:40:57 -0700 Subject: [PATCH 14/25] Removed tags array from event schema --- src/reducers/validate/eventSchema.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reducers/validate/eventSchema.js b/src/reducers/validate/eventSchema.js index 112f07e..312f7da 100644 --- a/src/reducers/validate/eventSchema.js +++ b/src/reducers/validate/eventSchema.js @@ -25,7 +25,6 @@ function createEventSchema (custom) { category_full: Joi.string().allow(''), associations: Joi.array(), sources: Joi.array(), - // tags: Joi.array().allow(''), comments: Joi.string().allow(''), time_display: Joi.string().allow(''), // nested From 5b2fd22405b0b722c33350e37c305663c86e59c3 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Fri, 11 Sep 2020 08:14:23 -0700 Subject: [PATCH 15/25] Minor typo with env var GRAPH_NONLOCATED --- example.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.config.js b/example.config.js index e88ba14..f257c79 100644 --- a/example.config.js +++ b/example.config.js @@ -29,7 +29,7 @@ module.exports = { USE_SEARCH: false, USE_SITES: false, USE_SHAPES: false, - GRAPH_NONLOHATED: false, + GRAPH_NONLOCATED: false, HIGHLIGHT_GROUPS: false } } From 8442ca7bb4bfd3fa4bf6bca992faa0d4fe8236c3 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Mon, 14 Sep 2020 08:12:57 -0700 Subject: [PATCH 16/25] Fixed linting issues --- src/common/constants.js | 2 +- src/components/Layout.js | 2 +- src/reducers/app.js | 2 +- src/reducers/validate/validators.js | 2 +- src/selectors/index.js | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/constants.js b/src/common/constants.js index 4cd536e..0537970 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -1,2 +1,2 @@ export const FILTER_MODE = 'FILTER' -export const NARRATIVE_MODE = 'NARRATIVE' \ No newline at end of file +export const NARRATIVE_MODE = 'NARRATIVE' diff --git a/src/components/Layout.js b/src/components/Layout.js index feaaa80..9abd446 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -139,7 +139,7 @@ class Dashboard extends React.Component { activeFilters = activeFilters.map(f => ({ name: f })) const evs = domain.events.filter(ev => { - let hasOne = false + let hasOne = false // add event if it has at least one matching filter for (let i = 0; i < activeFilters.length; i++) { if (ev.associations.includes(activeFilters[i].name)) { diff --git a/src/reducers/app.js b/src/reducers/app.js index 6498eb1..5ed4fbf 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -126,7 +126,7 @@ function toggleFilter (appState, action) { ...appState, associations: { ...appState.associations, - filters: newFilters, + filters: newFilters } } } diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js index 305ceb0..aa9f31d 100644 --- a/src/reducers/validate/validators.js +++ b/src/reducers/validate/validators.js @@ -7,7 +7,7 @@ import associationsSchema from './associationsSchema' import sourceSchema from './sourceSchema' import shapeSchema from './shapeSchema' -import { calcDatetime, capitalize, isFilterLeaf, isFilterDuplicate } from '../../common/utilities' +import { calcDatetime, capitalize } from '../../common/utilities' /* * Create an error notification object diff --git a/src/selectors/index.js b/src/selectors/index.js index c9592ed..1706b13 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -86,7 +86,7 @@ export const selectNarratives = createSelector( events.forEach(evt => { evt.associations.forEach(association => { const foundNarrative = narrativesMeta.find(narr => narr.id === association) - if (!!foundNarrative) { + if (foundNarrative) { const { id: narrId } = foundNarrative // initialise if (!narratives[narrId]) { narratives[narrId] = narrativeSkeleton(narrId) } @@ -104,7 +104,7 @@ export const selectNarratives = createSelector( const existingAssociatedNarrative = narrativesMeta.find(n => n.id === key) - if (!!existingAssociatedNarrative) { + if (existingAssociatedNarrative) { narratives[key] = { ...existingAssociatedNarrative, ...narratives[key] From d968280b1afd93c12c2a50aecb2ebc8a573c1824 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 15 Sep 2020 11:36:12 -0700 Subject: [PATCH 17/25] No associated events for a filter => alert shows when attempting to narrativise --- src/components/Layout.js | 12 ++++++++---- src/reducers/validate/validators.js | 6 ------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/Layout.js b/src/components/Layout.js index 9abd446..7451a58 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -104,7 +104,6 @@ class Dashboard extends React.Component { delete std.sources Object.values(std).forEach(ev => matchedEvents.push(ev)) } - this.props.actions.updateSelected(matchedEvents) } @@ -120,8 +119,8 @@ class Dashboard extends React.Component { } setNarrative (narrative) { - // only handleSelect if narrative is not null - if (narrative) { + // only handleSelect if narrative is not null and has associated events + if (narrative && narrative.steps.length >= 1) { this.handleSelect([ narrative.steps[0] ]) } this.props.actions.updateNarrative(narrative) @@ -150,6 +149,11 @@ class Dashboard extends React.Component { if (hasOne) return true return false }) + + if (evs.length === 0) { + alert('No associated events, cant narrativise') + return + } const name = activeFilters.map(f => f.name).join('-') const desc = activeFilters.map(f => f.description).join('\n\n') @@ -247,7 +251,7 @@ class Dashboard extends React.Component {
    ) } - + return (
    { - // NB: backwards compatibility with 'tags' for 'filters' - // if (domainKey === 'events') { - // if (!item.filters && !!item.tags) { - // item.filters = item.tags - // } - // } validateArrayItem(item, domainKey, schema) }) } From 7f12d1e86c300b20d767e8b753972d19d4f9006c Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 15 Sep 2020 11:36:52 -0700 Subject: [PATCH 18/25] Linting fixes --- src/components/Layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Layout.js b/src/components/Layout.js index 7451a58..120b6b2 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -149,7 +149,7 @@ class Dashboard extends React.Component { if (hasOne) return true return false }) - + if (evs.length === 0) { alert('No associated events, cant narrativise') return @@ -251,7 +251,7 @@ class Dashboard extends React.Component {
    ) } - + return (
    Date: Fri, 18 Sep 2020 07:52:08 -0700 Subject: [PATCH 19/25] Need to resolve minor issues with association duplicates; removing prototype tooltip; source not showing up and event gets deselected --- src/components/Layout.js | 1 + src/components/presentational/Map/Events.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/Layout.js b/src/components/Layout.js index 120b6b2..fe7c0e7 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -198,6 +198,7 @@ class Dashboard extends React.Component { onKeyDown (e) { const { narrative, selected } = this.props.app const { events } = this.props.domain + const prev = idx => { if (narrative === null) { this.handleSelect(events[idx - 1], 0) diff --git a/src/components/presentational/Map/Events.jsx b/src/components/presentational/Map/Events.jsx index f2fe193..3ef5271 100644 --- a/src/components/presentational/Map/Events.jsx +++ b/src/components/presentational/Map/Events.jsx @@ -1,6 +1,7 @@ import React from 'react' import { Portal } from 'react-portal' import colors from '../../../common/global.js' +import Tooltip from '../Tooltip' import { calcOpacity } from '../../../common/utilities' function MapEvents ({ From 6ae472b96f712c2348ad183d13f15c3c87a9ff7f Mon Sep 17 00:00:00 2001 From: efarooqui Date: Fri, 18 Sep 2020 10:50:23 -0700 Subject: [PATCH 20/25] Removing tooltip import --- src/components/presentational/Map/Events.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/presentational/Map/Events.jsx b/src/components/presentational/Map/Events.jsx index 3ef5271..f2fe193 100644 --- a/src/components/presentational/Map/Events.jsx +++ b/src/components/presentational/Map/Events.jsx @@ -1,7 +1,6 @@ import React from 'react' import { Portal } from 'react-portal' import colors from '../../../common/global.js' -import Tooltip from '../Tooltip' import { calcOpacity } from '../../../common/utilities' function MapEvents ({ From 3b4efc0cce5e634a92ef5574cabcecd398fe3b48 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Mon, 21 Sep 2020 21:37:11 -0700 Subject: [PATCH 21/25] Wrote new recursive function to accurately map filter paths to filter list in panel; need to fix selection of filters upon toggle --- src/components/Toolbar/FilterListPanel.js | 39 ++++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/components/Toolbar/FilterListPanel.js b/src/components/Toolbar/FilterListPanel.js index c4f4d22..ff67fa6 100644 --- a/src/components/Toolbar/FilterListPanel.js +++ b/src/components/Toolbar/FilterListPanel.js @@ -1,6 +1,7 @@ import React from 'react' import Checkbox from '../presentational/Checkbox' import copy from '../../common/data/copy.json' +import { merge } from 'lodash' /** recursively get an array of node keys to toggle */ function childrenToToggle (filter, activeFilters, parentOn) { @@ -19,21 +20,34 @@ function childrenToToggle (filter, activeFilters, parentOn) { return childKeys } +// function aggregatePaths (filters) { +// let aggregated = {} + +// filters.forEach(item => { +// let currentDepth = aggregated +// item.filter_paths.forEach(path => { +// if (!(path in aggregated)) { +// currentDepth[path] = {} +// } +// currentDepth = currentDepth[path] +// }) +// }) +// return aggregated +// } function aggregatePaths (filters) { - const aggregated = {} + function insertPath (children = {}, [headOfPath, ...remainder]) { + console.info(children, headOfPath, remainder) + let childKey = Object.keys(children).find(key => key === headOfPath) + if (!childKey) children[headOfPath] = {} + if (remainder.length > 0) insertPath(children[headOfPath], remainder) + return children + } - filters.forEach(item => { - let currentDepth = aggregated + const allPaths = [] + filters.forEach(filterItem => allPaths.push(filterItem.filter_paths)) - item.filter_paths.forEach(path => { - if (!(path in aggregated)) { - currentDepth[path] = {} - } - currentDepth = currentDepth[path] - }) - }) - - return aggregated + let aggregatedPaths = allPaths.reduce((children, path) => insertPath(children, path), {}) + return aggregatedPaths } function FilterListPanel ({ @@ -66,6 +80,7 @@ function FilterListPanel ({ function renderTree (filters) { const aggregatedFilterPaths = aggregatePaths(filters) + return (
    {Object.entries(aggregatedFilterPaths).map(filter => createNodeComponent(filter, 1))} From a4cd28d19fc0ee4bfc73e6489d0fc141b22d573f Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 22 Sep 2020 10:26:05 -0700 Subject: [PATCH 22/25] Debugging issues with broken sources --- src/components/CardStack.jsx | 1 - src/components/Layout.js | 19 +++++++++++++++---- src/components/Toolbar/FilterListPanel.js | 15 --------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index 1f6c371..65fd3f9 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -136,7 +136,6 @@ class CardStack extends React.Component { render () { const { isCardstack, selected, narrative, timelineDims } = this.props - // TODO: make '237px', which is the narrative header, less hard-coded const height = `calc(100% - 237px - ${timelineDims.height}px)` if (selected.length > 0) { diff --git a/src/components/Layout.js b/src/components/Layout.js index fe7c0e7..3c10a57 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -71,6 +71,16 @@ class Dashboard extends React.Component { ) } + filterEventDuplicates (events) { + const seen = Set() + const filteredEvents = events.filter(evt => { + if (!(seen.has(evt.id))) { + seen.add(evt.id) + return evt + } + }) + } + handleSelect (selected, axis) { const matchedEvents = [] const TIMELINE_AXIS = 0 @@ -86,8 +96,8 @@ class Dashboard extends React.Component { ptr >= 0 && (events[idx].datetime).getTime() === (events[ptr].datetime).getTime() ) { - matchedEvents.push(events[ptr]) - ptr -= 1 + matchedEvents.push(events[ptr]) + ptr -= 1 } // check events after ptr = idx + 1 @@ -96,14 +106,15 @@ class Dashboard extends React.Component { ptr < events.length && (events[idx].datetime).getTime() === (events[ptr].datetime).getTime() ) { - matchedEvents.push(events[ptr]) - ptr += 1 + matchedEvents.push(events[ptr]) + ptr += 1 } } else { // Map... const std = { ...selected } delete std.sources Object.values(std).forEach(ev => matchedEvents.push(ev)) } + console.info(matchedEvents) this.props.actions.updateSelected(matchedEvents) } diff --git a/src/components/Toolbar/FilterListPanel.js b/src/components/Toolbar/FilterListPanel.js index ff67fa6..9e4eb4b 100644 --- a/src/components/Toolbar/FilterListPanel.js +++ b/src/components/Toolbar/FilterListPanel.js @@ -20,23 +20,8 @@ function childrenToToggle (filter, activeFilters, parentOn) { return childKeys } -// function aggregatePaths (filters) { -// let aggregated = {} - -// filters.forEach(item => { -// let currentDepth = aggregated -// item.filter_paths.forEach(path => { -// if (!(path in aggregated)) { -// currentDepth[path] = {} -// } -// currentDepth = currentDepth[path] -// }) -// }) -// return aggregated -// } function aggregatePaths (filters) { function insertPath (children = {}, [headOfPath, ...remainder]) { - console.info(children, headOfPath, remainder) let childKey = Object.keys(children).find(key => key === headOfPath) if (!childKey) children[headOfPath] = {} if (remainder.length > 0) insertPath(children[headOfPath], remainder) From 2423a5a5ad252a6ac12bd71ad0b072b7af9670e6 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 22 Sep 2020 16:55:13 -0700 Subject: [PATCH 23/25] Issue with the difference between clicking on a card and clicking on the caret (ie is there an onClick functionality for the card?) --- src/common/utilities.js | 7 ++++--- src/components/Card.jsx | 18 +++++++++++++----- src/components/CardStack.jsx | 5 ++++- src/components/Layout.js | 15 ++++++++++----- src/selectors/index.js | 1 - src/store/initial.js | 2 +- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/common/utilities.js b/src/common/utilities.js index f384105..dac0f4a 100644 --- a/src/common/utilities.js +++ b/src/common/utilities.js @@ -74,9 +74,10 @@ export function insetSourceFrom (allSources) { if (!event.sources) { sources = [] } else { - sources = event.sources.map(id => ( - allSources.hasOwnProperty(id) ? allSources[id] : null - )) + sources = event.sources.map(src => { + const id = typeof src === 'object' ? src.id : src + return allSources.hasOwnProperty(id) ? allSources[id] : null + }) } return { ...event, diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 57ad170..3e9df64 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -13,7 +13,7 @@ class Card extends React.Component { constructor (props) { super(props) this.state = { - isOpen: false + isOpen: false, } } @@ -135,17 +135,25 @@ class Card extends React.Component { render () { const { isSelected, idx } = this.props - - return ( + console.info(this.props) + return (
  • { + console.info('getting clicked') + if (!this.state.isOpen) { + const selectedEventFormat = idx > 0 ? [this.props.event] : this.props.event + this.props.onSelect(selectedEventFormat, idx) + } else { + console.info('NOT OPEN') + } + }} > {this.renderMain()} {this.state.isOpen ? this.renderExtra() : null} - {isSelected ? this.renderCaret() : null} + {this.renderCaret()}
  • ) } diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index 65fd3f9..85e3351 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -59,6 +59,7 @@ class CardStack extends React.Component { return events.map((event, idx) => { const thisRef = React.createRef() this.refs[idx] = thisRef + return ( this.props.onSelect(idx)} + onSelect={this.props.onSelect} + idx={idx} features={this.props.features} />) }) @@ -79,6 +81,7 @@ class CardStack extends React.Component { renderSelectedCards () { const { selected } = this.props + if (selected.length > 0) { return this.renderCards(selected) } diff --git a/src/components/Layout.js b/src/components/Layout.js index a67e9b9..a90174f 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -83,6 +83,7 @@ class Dashboard extends React.Component { } handleSelect (selected, axis) { + console.info(selected) const matchedEvents = [] const TIMELINE_AXIS = 0 if (axis === TIMELINE_AXIS) { @@ -97,7 +98,9 @@ class Dashboard extends React.Component { ptr >= 0 && (events[idx].datetime).getTime() === (events[ptr].datetime).getTime() ) { - matchedEvents.push(events[ptr]) + if (events[ptr].id !== selected.id) { + matchedEvents.push(events[ptr]) + } ptr -= 1 } // check events after @@ -107,15 +110,16 @@ class Dashboard extends React.Component { ptr < events.length && (events[idx].datetime).getTime() === (events[ptr].datetime).getTime() ) { + if (events[ptr].id !== selected.id) { matchedEvents.push(events[ptr]) - ptr += 1 + } + ptr += 1 } - } else { // Map... + } else { // Map.. const std = { ...selected } delete std.sources Object.values(std).forEach(ev => matchedEvents.push(ev)) } - console.info(matchedEvents) this.props.actions.updateSelected(matchedEvents) } @@ -371,7 +375,8 @@ export default connect( state => ({ ...state, narrativeIdx: selectors.selectNarrativeIdx(state), - narratives: selectors.selectNarratives(state) + narratives: selectors.selectNarratives(state), + selected: selectors.selectSelected(state) }), mapDispatchToProps )(Dashboard) diff --git a/src/selectors/index.js b/src/selectors/index.js index 1706b13..4b5d2b6 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -267,7 +267,6 @@ export const selectSelected = createSelector( if (selected.length === 0) { return [] } - return selected.map(insetSourceFrom(sources)) } ) diff --git a/src/store/initial.js b/src/store/initial.js index bb130ed..beb475b 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -29,7 +29,7 @@ const initial = { */ app: { errors: { - source: null + source: false }, highlighted: null, selected: [], From 930aaaa7526da8121d1dfca306eb566c2b9ee092 Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 22 Sep 2020 17:12:09 -0700 Subject: [PATCH 24/25] Working onSelect with respect to caret toggle; onSelect doesnt fire off when caret is selected --- src/components/Card.jsx | 18 ++++++++---------- src/components/Layout.js | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 3e9df64..1a59040 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -27,6 +27,13 @@ class Card extends React.Component { return makeNiceDate(datetime) } + handleCardSelect (e) { + if (!e.target.className.includes('arrow-down')) { + const selectedEventFormat = this.props.idx > 0 ? [this.props.event] : this.props.event + this.props.onSelect(selectedEventFormat, this.props.idx) + } + } + renderSummary () { return ( { - console.info('getting clicked') - if (!this.state.isOpen) { - const selectedEventFormat = idx > 0 ? [this.props.event] : this.props.event - this.props.onSelect(selectedEventFormat, idx) - } else { - console.info('NOT OPEN') - } - }} + onClick={(e) => this.handleCardSelect(e)} > {this.renderMain()} {this.state.isOpen ? this.renderExtra() : null} diff --git a/src/components/Layout.js b/src/components/Layout.js index a90174f..e820f30 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -83,7 +83,6 @@ class Dashboard extends React.Component { } handleSelect (selected, axis) { - console.info(selected) const matchedEvents = [] const TIMELINE_AXIS = 0 if (axis === TIMELINE_AXIS) { From 13295c5f427173df6bab5a1c54bb4d1ec81cc3ec Mon Sep 17 00:00:00 2001 From: efarooqui Date: Tue, 22 Sep 2020 20:31:05 -0700 Subject: [PATCH 25/25] Linting fixes --- src/components/Card.jsx | 4 ++-- src/components/Layout.js | 18 ++++-------------- src/components/Toolbar/FilterListPanel.js | 1 - 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 1a59040..7e0ba18 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -13,7 +13,7 @@ class Card extends React.Component { constructor (props) { super(props) this.state = { - isOpen: false, + isOpen: false } } @@ -142,7 +142,7 @@ class Card extends React.Component { render () { const { isSelected, idx } = this.props - return ( + return (
  • { - if (!(seen.has(evt.id))) { - seen.add(evt.id) - return evt - } - }) - } - handleSelect (selected, axis) { const matchedEvents = [] const TIMELINE_AXIS = 0 @@ -97,10 +87,10 @@ class Dashboard extends React.Component { ptr >= 0 && (events[idx].datetime).getTime() === (events[ptr].datetime).getTime() ) { - if (events[ptr].id !== selected.id) { - matchedEvents.push(events[ptr]) - } - ptr -= 1 + if (events[ptr].id !== selected.id) { + matchedEvents.push(events[ptr]) + } + ptr -= 1 } // check events after ptr = idx + 1 diff --git a/src/components/Toolbar/FilterListPanel.js b/src/components/Toolbar/FilterListPanel.js index 9e4eb4b..c3447f1 100644 --- a/src/components/Toolbar/FilterListPanel.js +++ b/src/components/Toolbar/FilterListPanel.js @@ -1,7 +1,6 @@ import React from 'react' import Checkbox from '../presentational/Checkbox' import copy from '../../common/data/copy.json' -import { merge } from 'lodash' /** recursively get an array of node keys to toggle */ function childrenToToggle (filter, activeFilters, parentOn) {