From a39af029a3b29155c337d7d91708cce4687e945a Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 15:04:42 +0000 Subject: [PATCH 01/10] WIP: refactor Toolbar.jsx --- src/components/Dashboard.jsx | 17 +++++++------ src/components/NarrativeCard.js | 21 ++++++++++------ src/components/Toolbar.jsx | 44 ++++++++++++++------------------- src/js/utilities.js | 19 +++++++++++++- src/reducers/app.js | 44 +++++++++++++++++++-------------- src/scss/narrativecard.scss | 3 +++ 6 files changed, 88 insertions(+), 60 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 066cffd..7f20417 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -16,6 +16,8 @@ import Notification from './Notification.jsx'; import { parseDate } from '../js/utilities'; +import { injectNarrative } from '../js/utilities' + class Dashboard extends React.Component { constructor(props) { super(props); @@ -87,12 +89,13 @@ class Dashboard extends React.Component { return (
this.getCategoryColor(category) }} /> - {(this.props.app.narrative !== null) - ? - : '' - } + state, + // injectNarrative(0), mapDispatchToProps, )(Dashboard); diff --git a/src/components/NarrativeCard.js b/src/components/NarrativeCard.js index 67a6bbc..4526164 100644 --- a/src/components/NarrativeCard.js +++ b/src/components/NarrativeCard.js @@ -24,6 +24,8 @@ class NarrativeCard extends React.Component { } componentDidMount() { + if (!this.props.narrative) return + const step = this.props.narrative.steps[this.state.step]; this.props.onSelect([step]); } @@ -45,7 +47,7 @@ class NarrativeCard extends React.Component { renderClose() { return ( @@ -94,12 +88,12 @@ class Toolbar extends React.Component { return ''; } - renderToolbarTab(tabNum, label) { - const isActive = (this.state.tabNum === tabNum); + renderToolbarTab(_selected, label) { + const isActive = (this.state._selected === _selected); let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'; return ( -
{ this.toggleTab(tabNum); }}> +
{ this.selectTab(_selected); }}> timeline
{label}
@@ -126,12 +120,11 @@ class Toolbar extends React.Component { } renderToolbarPanels() { - let classes = (this.state.tabNum !== -1) ? 'toolbar-panels' : 'toolbar-panels folded'; - + let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded'; return (
{this.renderClosePanel()} - + {this.renderToolbarNarrativePanel()} {this.renderToolbarTagPanel()} @@ -142,12 +135,12 @@ class Toolbar extends React.Component { renderToolbarNavs() { if (this.props.narratives) { return this.props.narratives.map((nar, idx) => { - const isActive = (idx === this.state.tab); + const isActive = (idx === this.state._selected); let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'; return ( -
{ this.toggleTab(idx); }}> +
{ this.selectTab(idx); }}>
{nar.label}
); @@ -176,7 +169,8 @@ class Toolbar extends React.Component { } render() { - const isNarrative = isNotNullNorUndefined(this.props.narrative); + const { isNarrative } = this.props + return (
{this.renderToolbarTabs()} diff --git a/src/js/utilities.js b/src/js/utilities.js index 4ba3755..0da7f7b 100644 --- a/src/js/utilities.js +++ b/src/js/utilities.js @@ -77,7 +77,7 @@ export function formatter(datetime) { * Debugging function: put in place of a mapStateToProps function to * view that source modal by default */ -function injectSource(id) { +export function injectSource(id) { return state => ({ ...state, app: { @@ -87,3 +87,20 @@ function injectSource(id) { }) } +/** + * Debugging function: put in place of a mapStateToProps function to + * view that narrative modal by default + */ +export function injectNarrative(idx) { + return state => { + console.log(state.domain.narratives) + return { + ...state, + app: { + ...state.app, + narrative: state.domain.narratives.length ? state.domain.narratives[idx] : null + } + } + } +} + diff --git a/src/reducers/app.js b/src/reducers/app.js index c8a8ea6..3725fce 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -33,25 +33,33 @@ function updateSelected(appState, action) { } function updateNarrative(appState, action) { - if (action.narrative === null) { - return Object.assign({}, appState, { - narrative: action.narrative, - }); - } else { - const dates = action.narrative.steps.map(n => parseDate(n.timestamp).getTime()) - let minDate = Math.min(...dates); - let maxDate = Math.max(...dates); - // Add some margin to the datetime extent - minDate = minDate - ((maxDate - minDate) / 20); - maxDate = maxDate + ((maxDate - minDate) / 20); - - return Object.assign({}, appState, { - narrative: action.narrative, - filters: Object.assign({}, appState.filters, { - timerange: [new Date(minDate), new Date(maxDate)] - }), - }); + console.log(action.narrative) + return { + ...appState, + narrative: action.narrative } + // if (action.narrative === null) { + // console.log(action.narrative) + // return Object.assign({}, appState, { + // narrative: action.narrative, + // }); + // } else { + // const dates = action.narrative.steps.map(n => parseDate(n.timestamp).getTime()) + // let minDate = Math.min(...dates); + // let maxDate = Math.max(...dates); + // // Add some margin to the datetime extent + // minDate = minDate - ((maxDate - minDate) / 20); + // maxDate = maxDate + ((maxDate - minDate) / 20); + // + // const output = Object.assign({}, appState, { + // narrative: action.narrative, + // filters: Object.assign({}, appState.filters, { + // timerange: [new Date(minDate), new Date(maxDate)] + // }), + // }); + // console.log(output) + // return output + // } } function updateTagFilters(appState, action) { diff --git a/src/scss/narrativecard.scss b/src/scss/narrativecard.scss index ace0435..2987204 100644 --- a/src/scss/narrativecard.scss +++ b/src/scss/narrativecard.scss @@ -5,7 +5,10 @@ NARRATIVE INFO position: fixed; top: 10px; left: 10px; + // height: auto; + min-height: 500px; height: auto; + max-height: 500px; width: 370px; box-sizing: border-box; padding: 15px; From c75405f2ba46f927d792d8cae6b780e1f8494742 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 15:51:09 +0000 Subject: [PATCH 02/10] WIP: simplify narratives selection --- src/components/Dashboard.jsx | 29 ++++++-------------- src/components/MapEvents.jsx | 17 ++++++------ src/components/MapNarratives.jsx | 16 ++++++----- src/components/NarrativeCard.js | 4 +++ src/components/Toolbar.jsx | 14 ++++------ src/js/utilities.js | 7 +++++ src/reducers/app.js | 2 +- src/selectors/index.js | 47 ++++++++++++++++++-------------- 8 files changed, 69 insertions(+), 67 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 7f20417..ee8fac7 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -25,9 +25,6 @@ class Dashboard extends React.Component { this.handleViewSource = this.handleViewSource.bind(this) this.handleHighlight = this.handleHighlight.bind(this); this.handleSelect = this.handleSelect.bind(this); - this.handleSelectNarrative = this.handleSelectNarrative.bind(this); - this.handleTagFilter = this.handleTagFilter.bind(this); - this.updateTimerange = this.updateTimerange.bind(this); this.getCategoryColor = this.getCategoryColor.bind(this); this.eventsById = {} @@ -63,18 +60,6 @@ class Dashboard extends React.Component { } } - handleSelectNarrative(narrative) { - this.props.actions.updateNarrative(narrative); - } - - handleTagFilter(tag) { - this.props.actions.updateTagFilters(tag); - } - - updateTimerange(timeRange) { - this.props.actions.updateTimeRange(timeRange); - } - getCategoryColor(category='other') { return this.props.ui.style.categories[category] || this.props.ui.style.categories['other'] } @@ -90,30 +75,32 @@ class Dashboard extends React.Component {
this.getCategoryColor(category) }} /> byId.hasOwnProperty(e.id)) - if (eventsInNarrative.length <= 0) { - styleProps = { - ...styleProps, - fillOpacity: 0.1 - } - } + // TODO: logic to display narratives in Map + // const { byId } = this.props.narrative + // const eventsInNarrative = events.filter(e => byId.hasOwnProperty(e.id)) + // if (eventsInNarrative.length <= 0) { + // styleProps = { + // ...styleProps, + // fillOpacity: 0.1 + // } + // } } return ( diff --git a/src/components/MapNarratives.jsx b/src/components/MapNarratives.jsx index 2c9d777..bbd62ad 100644 --- a/src/components/MapNarratives.jsx +++ b/src/components/MapNarratives.jsx @@ -74,13 +74,15 @@ class MapNarratives extends React.Component { } renderNarrative(n) { - const steps = n.steps.slice(0, n.steps.length - 1); - - return ( - - {steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))} - - ) + // TODO: representation for narrative lines + // const steps = n.steps.slice(0, n.steps.length - 1); + // + // return ( + // + // {steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))} + // + // ) + return null } render() { diff --git a/src/components/NarrativeCard.js b/src/components/NarrativeCard.js index 4526164..e52ac5f 100644 --- a/src/components/NarrativeCard.js +++ b/src/components/NarrativeCard.js @@ -56,11 +56,15 @@ class NarrativeCard extends React.Component { } render() { + // no display if no narrative if (!this.props.narrative) return null + console.log(this.props.narrative) const { steps, current } = this.props.narrative + if (steps[current]) { const step = steps[current]; + console.log('here') return (
diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx index d6ef534..cb3b3da 100644 --- a/src/components/Toolbar.jsx +++ b/src/components/Toolbar.jsx @@ -5,7 +5,7 @@ import * as selectors from '../selectors' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import Search from './Search.jsx'; import TagListPanel from './TagListPanel.jsx'; -import ToolbarBottomActions from './ToolbarBottomActions.jsx'; +// import ToolbarBottomActions from './ToolbarBottomActions.jsx'; import copy from '../js/data/copy.json'; import { trimAndEllipse } from '../js/utilities.js'; @@ -46,8 +46,8 @@ class Toolbar extends React.Component { } goToNarrative(narrative) { - this.selectTab(-1) // set all unselected - this.props.onSelectNarrative(narrative); + this.selectTab(-1) // set all unselected within this component + this.props.methods.onSelectNarrative(narrative); } renderToolbarNarrativePanel() { @@ -112,9 +112,7 @@ class Toolbar extends React.Component { {this.renderToolbarTab(0, 'Focus stories')} {this.renderToolbarTab(1, 'Explore freely')}
- + {/* */}
) } @@ -161,9 +159,7 @@ class Toolbar extends React.Component { {this.renderToolbarTab(0, 'Narratives')} {(isTags) ? this.renderToolbarTab(1, 'Explore by tag') : ''}
- + {/* */}
) } diff --git a/src/js/utilities.js b/src/js/utilities.js index 0da7f7b..2c4b3c6 100644 --- a/src/js/utilities.js +++ b/src/js/utilities.js @@ -73,6 +73,13 @@ export function formatter(datetime) { return d3.timeFormat("%d %b, %H:%M")(datetime); } +export const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts); + +export function compareTimestamp (a, b) { + return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp)); +} + + /** * Debugging function: put in place of a mapStateToProps function to * view that source modal by default diff --git a/src/reducers/app.js b/src/reducers/app.js index 3725fce..0b37e77 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -33,11 +33,11 @@ function updateSelected(appState, action) { } function updateNarrative(appState, action) { - console.log(action.narrative) return { ...appState, narrative: action.narrative } + // if (action.narrative === null) { // console.log(action.narrative) // return Object.assign({}, appState, { diff --git a/src/selectors/index.js b/src/selectors/index.js index 0ef170d..5d7ac87 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -1,4 +1,5 @@ import { createSelector} from 'reselect' +import { parseTimestamp, compareTimestamp } from '../js/utilities' // Input selectors export const getEvents = state => state.domain.events; @@ -22,7 +23,6 @@ export const getTimeRange = state => state.app.filters.timerange; /** * Some handy helpers */ -const parseTimestamp = ts => d3.timeParse("%Y-%m-%dT%H:%M:%S")(ts); /** * Given an event and all tags, @@ -89,40 +89,45 @@ export const selectEvents = createSelector( */ export const selectNarratives = createSelector( [getEvents, getNarratives, getTagsFilter, getTimeRange], - (events, narrativeMetadata, tagFilters, timeRange) => { + (events, narrativesMeta, tagFilters, timeRange) => { const narratives = {}; - events.forEach((evt) => { + const narrativeSkeleton = id => ({ id, steps: [] }) + + /* populate narratives dict with events */ + events.forEach(evt => { const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters); const isTimeRanged = isTimeRangedIn(evt, timeRange); const isInNarrative = evt.narratives.length > 0; - evt.narratives.map(narrative => { - if (!narratives[narrative]) { - narratives[narrative] = { id: narrative, steps: [], byId: {} }; - } + evt.narratives.forEach(narrative => { + // initialise + if (!narratives[narrative]) + narratives[narrative] = narrativeSkeleton(narrative) - if (isInNarrative) { - narratives[narrative].steps.push(evt); - narratives[narrative].byId[evt.id] = { next: null, prev: null }; - } + // add evt to steps + if (isInNarrative) + narratives[narrative].steps.push(evt) }) }); - Object.keys(narratives).forEach((key) => { + + /* sort steps by time */ + Object.keys(narratives).forEach(key => { const steps = narratives[key].steps; - steps.sort((a, b) => { - return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp)); - }); + steps.sort(compareTimestamp); - steps.forEach((step, i) => { - narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null; - narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null; - }); + // steps.forEach((step, i) => { + // narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null; + // narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null; + // }); - if (narrativeMetadata.find(n => n.id === key)) { - narratives[key] = Object.assign(narrativeMetadata.find(n => n.id === key), narratives[key]); + if (narrativesMeta.find(n => n.id === key)) { + narratives[key] = { + ...narrativesMeta.find(n => n.id === key), + ...narratives[key] + } } }); From e6742d0b046f19e8f5967beb64b54903ee97e3a5 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 16:11:03 +0000 Subject: [PATCH 03/10] factor step state of narrative from NarrativeCard comp to redux --- src/components/NarrativeCard.js | 5 +- src/reducers/app.js | 28 ++------ src/selectors/index.js | 117 +++++++++++++++++--------------- src/store/initial.js | 3 + 4 files changed, 73 insertions(+), 80 deletions(-) diff --git a/src/components/NarrativeCard.js b/src/components/NarrativeCard.js index e52ac5f..5d771a5 100644 --- a/src/components/NarrativeCard.js +++ b/src/components/NarrativeCard.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux' +import { selectActiveNarrative } from '../selectors' class NarrativeCard extends React.Component { @@ -59,12 +60,10 @@ class NarrativeCard extends React.Component { // no display if no narrative if (!this.props.narrative) return null - console.log(this.props.narrative) const { steps, current } = this.props.narrative if (steps[current]) { const step = steps[current]; - console.log('here') return (
@@ -89,7 +88,7 @@ class NarrativeCard extends React.Component { function mapStateToProps(state) { return { - narrative: state.app.narrative + narrative: selectActiveNarrative(state) } } export default connect(mapStateToProps)(NarrativeCard); diff --git a/src/reducers/app.js b/src/reducers/app.js index 0b37e77..1904703 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -35,31 +35,11 @@ function updateSelected(appState, action) { function updateNarrative(appState, action) { return { ...appState, - narrative: action.narrative + narrative: action.narrative, + narrativeState: { + current: 0 + } } - - // if (action.narrative === null) { - // console.log(action.narrative) - // return Object.assign({}, appState, { - // narrative: action.narrative, - // }); - // } else { - // const dates = action.narrative.steps.map(n => parseDate(n.timestamp).getTime()) - // let minDate = Math.min(...dates); - // let maxDate = Math.max(...dates); - // // Add some margin to the datetime extent - // minDate = minDate - ((maxDate - minDate) / 20); - // maxDate = maxDate + ((maxDate - minDate) / 20); - // - // const output = Object.assign({}, appState, { - // narrative: action.narrative, - // filters: Object.assign({}, appState.filters, { - // timerange: [new Date(minDate), new Date(maxDate)] - // }), - // }); - // console.log(output) - // return output - // } } function updateTagFilters(appState, action) { diff --git a/src/selectors/index.js b/src/selectors/index.js index 5d7ac87..76cb8ad 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -2,23 +2,25 @@ import { createSelector} from 'reselect' import { parseTimestamp, compareTimestamp } from '../js/utilities' // Input selectors -export const getEvents = state => state.domain.events; -export const getLocations = state => state.domain.locations; -export const getCategories = state => state.domain.categories; -export const getNarratives = state => state.domain.narratives; -export const getSelected = state => state.app.selected; +export const getEvents = state => state.domain.events +export const getLocations = state => state.domain.locations +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 getSelected = state => state.app.selected export const getSites = (state) => { - if (process.env.features.USE_SITES) return state.domain.sites; - return []; + if (process.env.features.USE_SITES) return state.domain.sites + return [] } export const getSources = state => { - if (process.env.features.USE_SOURCES) return state.domain.sources; - return []; + if (process.env.features.USE_SOURCES) return state.domain.sources + return [] } -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 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 /** * Some handy helpers @@ -30,11 +32,11 @@ export const getTimeRange = state => state.app.filters.timerange; */ function isTaggedIn(event, tagFilters) { if (event.tags) { - const tagsInEvent = event.tags.split(","); + const tagsInEvent = event.tags.split(",") const isTagged = tagsInEvent.some((tag) => { - return tagFilters.find(tF => (tF.key === tag && tF.active)); - }); - return isTagged; + return tagFilters.find(tF => (tF.key === tag && tF.active)) + }) + return isTagged } else { return false } @@ -48,7 +50,7 @@ function isNoTags(tagFilters) { tagFilters.length === 0 || !process.env.features.USE_TAGS || tagFilters.every(t => !t.active) - ); + ) } /** @@ -59,7 +61,7 @@ function isTimeRangedIn(event, timeRange) { return ( timeRange[0] < parseTimestamp(event.timestamp) && parseTimestamp(event.timestamp) < timeRange[1] - ); + ) } /** @@ -71,17 +73,17 @@ export const selectEvents = createSelector( (events, tagFilters, timeRange) => { return events.reduce((acc, event) => { - const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters); - const isTimeRanged = isTimeRangedIn(event, timeRange); + const isTagged = isTaggedIn(event, tagFilters) || isNoTags(tagFilters) + const isTimeRanged = isTimeRangedIn(event, timeRange) if (isTimeRanged && isTagged) { - const eventClone = Object.assign({}, event); - acc[event.id] = eventClone; + const eventClone = Object.assign({}, event) + acc[event.id] = eventClone } - return acc; - }, []); -}); + return acc + }, []) +}) /** * Of all available events, selects those that fall within the time range, @@ -91,14 +93,14 @@ export const selectNarratives = createSelector( [getEvents, getNarratives, getTagsFilter, getTimeRange], (events, narrativesMeta, tagFilters, timeRange) => { - const narratives = {}; + const narratives = {} const narrativeSkeleton = id => ({ id, steps: [] }) /* populate narratives dict with events */ events.forEach(evt => { - const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters); - const isTimeRanged = isTimeRangedIn(evt, timeRange); - const isInNarrative = evt.narratives.length > 0; + const isTagged = isTaggedIn(evt, tagFilters) || isNoTags(tagFilters) + const isTimeRanged = isTimeRangedIn(evt, timeRange) + const isInNarrative = evt.narratives.length > 0 evt.narratives.forEach(narrative => { // initialise @@ -109,19 +111,19 @@ export const selectNarratives = createSelector( if (isInNarrative) narratives[narrative].steps.push(evt) }) - }); + }) /* sort steps by time */ Object.keys(narratives).forEach(key => { - const steps = narratives[key].steps; + const steps = narratives[key].steps - steps.sort(compareTimestamp); + steps.sort(compareTimestamp) // steps.forEach((step, i) => { - // narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null; - // narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null; - // }); + // narratives[key].byId[step.id].next = (i < steps.length - 2) ? steps[i + 1] : null + // narratives[key].byId[step.id].prev = (i > 0) ? steps[i - 1] : null + // }) if (narrativesMeta.find(n => n.id === key)) { narratives[key] = { @@ -129,11 +131,20 @@ export const selectNarratives = createSelector( ...narratives[key] } } - }); + }) - return Object.values(narratives); -}); + return Object.values(narratives) +}) +/** Aggregate information about the narrative and the current step into + * a single object. If narrative is null, the whole object is null. + */ +export const selectActiveNarrative = createSelector( + [getActiveNarrative, getActiveStep], + (narrative, current) => !!narrative + ? { ...narrative, current } + : null +) /** * 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 @@ -142,12 +153,12 @@ export const selectLocations = createSelector( [selectEvents], (events) => { - const selectedLocations = {}; + const selectedLocations = {} events.forEach(event => { - const location = event.location; + const location = event.location if (selectedLocations[location]) { - selectedLocations[location].events.push(event); + selectedLocations[location].events.push(event) } else { selectedLocations[location] = { label: location, @@ -158,9 +169,9 @@ export const selectLocations = createSelector( } }) - return Object.values(selectedLocations); + return Object.values(selectedLocations) } -); +) /** * Of all the sources, select those that are relevant to the selected events. @@ -176,7 +187,7 @@ export const selectSelected = createSelector( const srcs = selected .map(e => e.sources) .map(_sources => { - if (!_sources) return []; + if (!_sources) return [] return _sources.map(id => ( sources.hasOwnProperty(id) ? sources[id] : null )) @@ -196,7 +207,7 @@ export const selectSelected = createSelector( export const selectCategories = createSelector( [getCategories], (categories) => categories -); +) /** @@ -206,23 +217,23 @@ export const selectCategories = createSelector( export const selectTagList = createSelector( [getTagTree], (tags) => { - const tagList = []; - let depth = 0; + const tagList = [] + let depth = 0 function traverseNode(node, depth) { - node.active = (!node.hasOwnProperty('active')) ? false : node.active; - node.depth = 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 + 1); - }); + traverseNode(childNode, depth + 1) + }) } } if (tags && tags !== undefined) { if (tags.key && tags.children) traverseNode(tags, depth) } - return tagList; + return tagList } ) diff --git a/src/store/initial.js b/src/store/initial.js index 1c0a624..c18baa0 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -33,6 +33,9 @@ const initial = { selected: [], source: null, narrative: null, + narrativeState: { + current: null + }, filters: { timerange: [ d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"), From 125bd06f598c8eb57e60338aa7371124bbdb8b9b Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 16:40:38 +0000 Subject: [PATCH 04/10] WIP: increment narrative through redux --- src/actions/index.js | 14 ++++ src/components/Dashboard.jsx | 46 +++++++------ src/components/NarrativeCard.js | 111 ++++++++++++-------------------- src/js/utilities.js | 18 ------ src/reducers/app.js | 2 +- 5 files changed, 80 insertions(+), 111 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index 3a5e890..7f39a6d 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -213,6 +213,20 @@ export function updateNarrative(narrative) { } } +export const INCREMENT_NARRATIVE_CURRENT = 'INCREMENT_NARRATIVE_CURRENT'; +export function incrementNarrativeCurrent() { + return { + type: INCREMENT_NARRATIVE_CURRENT + } +} + +export const DECREMENT_NARRATIVE_CURRENT = 'DECREMENT_NARRATIVE_CURRENT'; +export function decrementNarrativeCurrent() { + return { + type: DECREMENT_NARRATIVE_CURRENT + } +} + export const RESET_ALLFILTERS = 'RESET_ALLFILTERS' export function resetAllFilters() { return { diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index ee8fac7..f73580a 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -71,64 +71,69 @@ class Dashboard extends React.Component { } render() { + const { actions, app, domain, ui } = this.props return (
this.getCategoryColor(category) }} /> this.props.actions.updateSelected([])} + onToggleCardstack={() => actions.updateSelected([])} getNarrativeLinks={event => this.getNarrativeLinks(event)} getCategoryColor={category => this.getCategoryColor(category)} /> this.props.actions.toggleInfoPopup()} + ui={ui} + app={app} + toggle={() => actions.toggleInfoPopup()} /> - {this.props.app.source ? ( + {app.source ? ( { - this.props.actions.updateSource(null)} + actions.updateSource(null)} } /> ) : null}
); @@ -153,6 +158,5 @@ function injectSource(id) { export default connect( state => state, - // injectNarrative(0), mapDispatchToProps, )(Dashboard); diff --git a/src/components/NarrativeCard.js b/src/components/NarrativeCard.js index 5d771a5..ab3fc87 100644 --- a/src/components/NarrativeCard.js +++ b/src/components/NarrativeCard.js @@ -2,87 +2,56 @@ import React from 'react'; import { connect } from 'react-redux' import { selectActiveNarrative } from '../selectors' -class NarrativeCard extends React.Component { - - constructor() { - super(); - - this.state = { - step: 0 - } - } - - goToPrevKeyFrame() { - if (this.state.step > 0) { - this.setState({ step: this.state.step - 1 }); - } - } - - goToNextKeyFrame() { - if (this.state.step < this.props.narrative.steps.length - 1) { - this.setState({ step: this.state.step + 1 }); - } - } - - componentDidMount() { - if (!this.props.narrative) return - - const step = this.props.narrative.steps[this.state.step]; - this.props.onSelect([step]); - } - - componentDidUpdate(prevProps, prevState) { - if (prevProps.narrative === this.props.narrative && this.state.step !== prevState.step) { - const step = this.props.narrative.steps[this.state.step]; - this.props.onSelect([step]); - } else if (prevProps.narrative !== this.props.narrative && this.props.narrative !== null) { - this.setState({ - step: 0 - }, () => { - const step = this.props.narrative.steps[this.state.step]; - this.props.onSelect([step]); - }); - } - } - - renderClose() { +function NarrativeCard ({ narrative, methods }) { + const { onSelectNarrative, onNext, onPrev } = methods + function renderClose() { return ( ) } - render() { - // no display if no narrative - if (!this.props.narrative) return null - - const { steps, current } = this.props.narrative - - if (steps[current]) { - const step = steps[current]; - - return ( -
- {this.renderClose()} -

{this.props.narrative.label}

-

{this.props.narrative.description}

-
- location_on - {this.state.step + 1}/{steps.length}. {step.location} -
-
-
this.goToPrevKeyFrame()}>←
-
= this.props.narrative.steps.length - 1) ? 'disabled ' : ''} action`} onClick={() => this.goToNextKeyFrame()}>→
-
+ function _renderActions(current, steps) { + return ( +
+
- ); - } else { - return null - } +
= steps.length - 1) ? 'disabled ' : ''} action`} + onClick={onNext}>→ +
+
+ ) + } + + // no display if no narrative + if (!narrative) return null + + const { steps, current } = narrative + + if (steps[current]) { + const step = steps[current]; + + return ( +
+ {renderClose()} +

{narrative.label}

+

{narrative.description}

+
+ location_on + {current + 1}/{steps.length}. {step.location} +
+ {_renderActions(current, steps)} +
+ ); + } else { + return null } } diff --git a/src/js/utilities.js b/src/js/utilities.js index 2c4b3c6..751a93d 100644 --- a/src/js/utilities.js +++ b/src/js/utilities.js @@ -93,21 +93,3 @@ export function injectSource(id) { } }) } - -/** - * Debugging function: put in place of a mapStateToProps function to - * view that narrative modal by default - */ -export function injectNarrative(idx) { - return state => { - console.log(state.domain.narratives) - return { - ...state, - app: { - ...state.app, - narrative: state.domain.narratives.length ? state.domain.narratives[idx] : null - } - } - } -} - diff --git a/src/reducers/app.js b/src/reducers/app.js index 1904703..d5ef6d1 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -37,7 +37,7 @@ function updateNarrative(appState, action) { ...appState, narrative: action.narrative, narrativeState: { - current: 0 + current: !!action.narrative ? 0 : null } } } From 1126d54def2fdd3c7297b469498198ebd4e5140b Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 16:48:03 +0000 Subject: [PATCH 05/10] next/prev functionality on narrative card --- src/components/NarrativeCard.js | 20 +++--- src/reducers/app.js | 108 +++++++++++++++++++------------- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/components/NarrativeCard.js b/src/components/NarrativeCard.js index ab3fc87..721f727 100644 --- a/src/components/NarrativeCard.js +++ b/src/components/NarrativeCard.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React from 'react' import { connect } from 'react-redux' import { selectActiveNarrative } from '../selectors' @@ -8,7 +8,7 @@ function NarrativeCard ({ narrative, methods }) { return ( @@ -16,15 +16,17 @@ function NarrativeCard ({ narrative, methods }) { } function _renderActions(current, steps) { + const prevExists = current !== 0 + const nextExists = current < steps.length - 1 return (
← + className={`${prevExists ? '' : 'disabled'} action`} + onClick={prevExists ? onPrev : null}>←
= steps.length - 1) ? 'disabled ' : ''} action`} - onClick={onNext}>→ + className={`${nextExists ? '' : 'disabled'} action`} + onClick={nextExists ? onNext : null}>→
) @@ -36,7 +38,7 @@ function NarrativeCard ({ narrative, methods }) { const { steps, current } = narrative if (steps[current]) { - const step = steps[current]; + const step = steps[current] return (
@@ -49,7 +51,7 @@ function NarrativeCard ({ narrative, methods }) { {_renderActions(current, steps)}
- ); + ) } else { return null } @@ -60,4 +62,4 @@ function mapStateToProps(state) { narrative: selectActiveNarrative(state) } } -export default connect(mapStateToProps)(NarrativeCard); +export default connect(mapStateToProps)(NarrativeCard) diff --git a/src/reducers/app.js b/src/reducers/app.js index d5ef6d1..aed37e2 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -1,6 +1,6 @@ -import initial from '../store/initial.js'; +import initial from '../store/initial.js' -import { parseDate } from '../js/utilities.js'; +import { parseDate } from '../js/utilities.js' import { UPDATE_HIGHLIGHTED, @@ -8,6 +8,8 @@ import { UPDATE_TAGFILTERS, UPDATE_TIMERANGE, UPDATE_NARRATIVE, + INCREMENT_NARRATIVE_CURRENT, + DECREMENT_NARRATIVE_CURRENT, UPDATE_SOURCE, RESET_ALLFILTERS, TOGGLE_LANGUAGE, @@ -18,18 +20,18 @@ import { TOGGLE_NOTIFICATIONS, FETCH_ERROR, FETCH_SOURCE_ERROR, -} from '../actions'; +} from '../actions' function updateHighlighted(appState, action) { return Object.assign({}, appState, { highlighted: action.highlighted - }); + }) } function updateSelected(appState, action) { return Object.assign({}, appState, { selected: action.selected - }); + }) } function updateNarrative(appState, action) { @@ -42,27 +44,45 @@ function updateNarrative(appState, action) { } } +function incrementNarrativeCurrent(appState, action) { + return { + ...appState, + narrativeState: { + current: appState.narrativeState.current += 1 + } + } +} + +function decrementNarrativeCurrent(appState, action) { + return { + ...appState, + narrativeState: { + current: appState.narrativeState.current -= 1 + } + } +} + function updateTagFilters(appState, action) { - const tagFilters = appState.filters.tags.slice(0); + const tagFilters = appState.filters.tags.slice(0) const nextActiveState = action.tag.active function traverseNode(node) { - const tagFilter = tagFilters.find(tF => tF.key === node.key); - node.active = nextActiveState; - if (!tagFilter) tagFilters.push(node); + const tagFilter = tagFilters.find(tF => tF.key === node.key) + node.active = nextActiveState + if (!tagFilter) tagFilters.push(node) if (node && Object.keys(node.children).length > 0) { - Object.values(node.children).forEach((childNode) => { traverseNode(childNode); }); + Object.values(node.children).forEach((childNode) => { traverseNode(childNode) }) } } - traverseNode(action.tag); + traverseNode(action.tag) return Object.assign({}, appState, { filters: Object.assign({}, appState.filters, { tags: tagFilters }) - }); + }) } function updateTimeRange(appState, action) { // XXX @@ -70,7 +90,7 @@ function updateTimeRange(appState, action) { // XXX filters: Object.assign({}, appState.filters, { timerange: action.timerange }), - }); + }) } function resetAllFilters(appState) { // XXX @@ -84,26 +104,26 @@ function resetAllFilters(appState) { // XXX ], }), selected: [], - }); + }) } function toggleLanguage(appState, action) { - let otherLanguage = (appState.language === 'es-MX') ? 'en-US' : 'es-MX'; + let otherLanguage = (appState.language === 'es-MX') ? 'en-US' : 'es-MX' return Object.assign({}, appState, { language: action.language || otherLanguage - }); + }) } function toggleMapView(appState, action) { - const isLayerInView = !appState.views[layer]; - const newViews = {}; - newViews[layer] = isLayerInView; - const views = Object.assign({}, appState.views, newViews); + const isLayerInView = !appState.views[layer] + const newViews = {} + newViews[layer] = isLayerInView + const views = Object.assign({}, appState.views, newViews) return Object.assign({}, appState, { filters: Object.assign({}, appState.filters, { views }) - }); + }) } function updateSource(appState, action) { @@ -126,7 +146,7 @@ function toggleFetchingDomain(appState, action) { flags: Object.assign({}, appState.flags, { isFetchingDomain: !appState.flags.isFetchingDomain }) - }); + }) } function toggleFetchingSources(appState, action) { @@ -134,7 +154,7 @@ function toggleFetchingSources(appState, action) { flags: Object.assign({}, appState.flags, { isFetchingSources: !appState.flags.isFetchingSources }) - }); + }) } function toggleInfoPopup(appState, action) { @@ -142,7 +162,7 @@ function toggleInfoPopup(appState, action) { flags: Object.assign({}, appState.flags, { isInfopopup: !appState.flags.isInfopopup }) - }); + }) } function toggleNotifications(appState, action) { @@ -150,7 +170,7 @@ function toggleNotifications(appState, action) { flags: Object.assign({}, appState.flags, { isNotification: !appState.flags.isNotification }) - }); + }) } function fetchSourceError(appState, action) { @@ -168,38 +188,42 @@ function fetchSourceError(appState, action) { function app(appState = initial.app, action) { switch (action.type) { case UPDATE_HIGHLIGHTED: - return updateHighlighted(appState, action); + return updateHighlighted(appState, action) case UPDATE_SELECTED: - return updateSelected(appState, action); + return updateSelected(appState, action) case UPDATE_TAGFILTERS: - return updateTagFilters(appState, action); + return updateTagFilters(appState, action) case UPDATE_TIMERANGE: - return updateTimeRange(appState, action); + return updateTimeRange(appState, action) case UPDATE_NARRATIVE: - return updateNarrative(appState, action); + return updateNarrative(appState, action) + case INCREMENT_NARRATIVE_CURRENT: + return incrementNarrativeCurrent(appState, action) + case DECREMENT_NARRATIVE_CURRENT: + return decrementNarrativeCurrent(appState, action) case UPDATE_SOURCE: - return updateSource(appState, action); + return updateSource(appState, action) case RESET_ALLFILTERS: - return resetAllFilters(appState, action); + return resetAllFilters(appState, action) case TOGGLE_LANGUAGE: - return toggleLanguage(appState, action); + return toggleLanguage(appState, action) case TOGGLE_MAPVIEW: - return toggleMapView(appState, action); + return toggleMapView(appState, action) case FETCH_ERROR: - return fetchError(appState, action); + return fetchError(appState, action) case TOGGLE_FETCHING_DOMAIN: - return toggleFetchingDomain(appState, action); + return toggleFetchingDomain(appState, action) case TOGGLE_FETCHING_SOURCES: - return toggleFetchingSources(appState, action); + return toggleFetchingSources(appState, action) case TOGGLE_INFOPOPUP: - return toggleInfoPopup(appState, action); + return toggleInfoPopup(appState, action) case TOGGLE_NOTIFICATIONS: - return toggleNotifications(appState, action); + return toggleNotifications(appState, action) case FETCH_SOURCE_ERROR: - return fetchSourceError(appState, action); + return fetchSourceError(appState, action) default: - return appState; + return appState } } -export default app; +export default app From 58b37105f5ebd150d2a66fb1a11e9f73fca8be61 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 17:17:50 +0000 Subject: [PATCH 06/10] narrative movement fully redux-ized --- src/components/Dashboard.jsx | 93 +++++++++++++++++++------------- src/components/MapEvents.jsx | 19 +++---- src/components/MapNarratives.jsx | 60 ++++++++++----------- 3 files changed, 96 insertions(+), 76 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index f73580a..9986768 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -1,31 +1,33 @@ -import React from 'react'; +import React from 'react' -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import * as actions from '../actions'; +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import * as actions from '../actions' -import SourceOverlay from './SourceOverlay.jsx'; -import LoadingOverlay from './presentational/LoadingOverlay'; -import Map from './Map.jsx'; -import Toolbar from './Toolbar.jsx'; -import CardStack from './CardStack.jsx'; -import NarrativeCard from './NarrativeCard.js'; -import InfoPopUp from './InfoPopup.jsx'; -import Timeline from './Timeline.jsx'; -import Notification from './Notification.jsx'; +import SourceOverlay from './SourceOverlay.jsx' +import LoadingOverlay from './presentational/LoadingOverlay' +import Map from './Map.jsx' +import Toolbar from './Toolbar.jsx' +import CardStack from './CardStack.jsx' +import NarrativeCard from './NarrativeCard.js' +import InfoPopUp from './InfoPopup.jsx' +import Timeline from './Timeline.jsx' +import Notification from './Notification.jsx' -import { parseDate } from '../js/utilities'; +import { parseDate } from '../js/utilities' import { injectNarrative } from '../js/utilities' class Dashboard extends React.Component { constructor(props) { - super(props); + super(props) this.handleViewSource = this.handleViewSource.bind(this) - this.handleHighlight = this.handleHighlight.bind(this); - this.handleSelect = this.handleSelect.bind(this); - this.getCategoryColor = this.getCategoryColor.bind(this); + this.handleHighlight = this.handleHighlight.bind(this) + this.setNarrative = this.setNarrative.bind(this) + this.moveInNarrative = this.moveInNarrative.bind(this) + this.handleSelect = this.handleSelect.bind(this) + this.getCategoryColor = this.getCategoryColor.bind(this) this.eventsById = {} } @@ -33,18 +35,18 @@ class Dashboard extends React.Component { componentDidMount() { if (!this.props.app.isMobile) { this.props.actions.fetchDomain() - .then(domain => this.props.actions.updateDomain(domain)); + .then(domain => this.props.actions.updateDomain(domain)) } } handleHighlight(highlighted) { - this.props.actions.updateHighlighted((highlighted) ? highlighted : null); + this.props.actions.updateHighlighted((highlighted) ? highlighted : null) } getEventById(eventId) { - if (this.eventsById[eventId]) return this.eventsById[eventId]; - this.eventsById[eventId] = this.props.domain.events.find(ev => ev.id === eventId); - return this.eventsById[eventId]; + if (this.eventsById[eventId]) return this.eventsById[eventId] + this.eventsById[eventId] = this.props.domain.events.find(ev => ev.id === eventId) + return this.eventsById[eventId] } handleViewSource(source) { @@ -53,7 +55,7 @@ class Dashboard extends React.Component { handleSelect(selected) { if (selected) { - let eventsToSelect = selected.map(event => this.getEventById(event.id)); + let eventsToSelect = selected.map(event => this.getEventById(event.id)) eventsToSelect = eventsToSelect.sort((a, b) => parseDate(a.timestamp) - parseDate(b.timestamp)) this.props.actions.updateSelected(eventsToSelect) @@ -65,9 +67,29 @@ class Dashboard extends React.Component { } getNarrativeLinks(event) { - const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative); - if (narrative) return narrative.byId[event.id]; - return null; + const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative) + if (narrative) return narrative.byId[event.id] + return null + } + + setNarrative(narrative) { + // only handleSelect if narrative is not null + if (!!narrative) + this.handleSelect([ narrative.steps[0] ]) + this.props.actions.updateNarrative(narrative) + } + moveInNarrative(amt) { + const { current } = this.props.app.narrativeState + const { narrative } = this.props.app + + if (amt === 1) { + this.handleSelect([ narrative.steps[current + 1] ]) + this.props.actions.incrementNarrativeCurrent() + } + if (amt === -1) { + this.handleSelect([ narrative.steps[current - 1] ]) + this.props.actions.decrementNarrativeCurrent() + } } render() { @@ -78,14 +100,14 @@ class Dashboard extends React.Component { isNarrative={!!app.narrative} methods={{ onFilter: actions.updateTagFilters, - onSelectNarrative: actions.updateNarrative + onSelectNarrative: this.setNarrative }} /> @@ -98,10 +120,9 @@ class Dashboard extends React.Component { /> this.moveInNarrative(1), + onPrev: () => this.moveInNarrative(-1), + onSelectNarrative: this.setNarrative }} />
- ); + ) } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actions, dispatch) - }; + } } function injectSource(id) { @@ -159,4 +180,4 @@ function injectSource(id) { export default connect( state => state, mapDispatchToProps, -)(Dashboard); +)(Dashboard) diff --git a/src/components/MapEvents.jsx b/src/components/MapEvents.jsx index 6fe3e00..70cac85 100644 --- a/src/components/MapEvents.jsx +++ b/src/components/MapEvents.jsx @@ -33,15 +33,16 @@ class MapEvents extends React.Component { }) if (this.props.narrative) { - // TODO: logic to display narratives in Map - // const { byId } = this.props.narrative - // const eventsInNarrative = events.filter(e => byId.hasOwnProperty(e.id)) - // if (eventsInNarrative.length <= 0) { - // styleProps = { - // ...styleProps, - // fillOpacity: 0.1 - // } - // } + const { steps } = this.props.narrative + const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id) + const eventsInNarrative = events.filter(onlyIfInNarrative) + + if (eventsInNarrative.length <= 0) { + styleProps = { + ...styleProps, + fillOpacity: 0.1 + } + } } return ( diff --git a/src/components/MapNarratives.jsx b/src/components/MapNarratives.jsx index bbd62ad..8b92bb8 100644 --- a/src/components/MapNarratives.jsx +++ b/src/components/MapNarratives.jsx @@ -1,42 +1,42 @@ -import React from 'react'; -import { Portal } from 'react-portal'; +import React from 'react' +import { Portal } from 'react-portal' class MapNarratives extends React.Component { projectPoint(location) { - const latLng = new L.LatLng(location[0], location[1]); + const latLng = new L.LatLng(location[0], location[1]) return { x: this.props.map.latLngToLayerPoint(latLng).x + this.props.mapTransformX, y: this.props.map.latLngToLayerPoint(latLng).y + this.props.mapTransformY - }; + } } getNarrativeStyle(narrativeId) { const styleName = (narrativeId && narrativeId in this.props.narrativeProps) ? narrativeId - : 'default'; - return this.props.narrativeProps[styleName]; + : 'default' + return this.props.narrativeProps[styleName] } getStrokeWidth(narrative, step) { - if (!step) return 0; - return this.getNarrativeStyle(narrative.id).strokeWidth; + if (!step) return 0 + return this.getNarrativeStyle(narrative.id).strokeWidth } getStrokeDashArray(narrative, step) { - if (!step) return 'none'; - return (this.getNarrativeStyle(narrative.id).style === 'dotted') ? "2px 5px" : 'none'; + if (!step) return 'none' + return (this.getNarrativeStyle(narrative.id).style === 'dotted') ? "2px 5px" : 'none' } getStroke(narrative, step) { - if (!step || this.props.narrative === null) return 'none'; - return this.getNarrativeStyle(narrative.id).stroke; + if (!step || this.props.narrative === null) return 'none' + return this.getNarrativeStyle(narrative.id).stroke } getStrokeOpacity(narrative, step) { - if (this.props.narrative === null) return 0; - if (!step || narrative.id !== this.props.narrative.id) return 0.1; - return 1; + if (this.props.narrative === null) return 0 + if (!step || narrative.id !== this.props.narrative.id) return 0.1 + return 1 } hasNoLocation(step) { @@ -44,14 +44,14 @@ class MapNarratives extends React.Component { } renderNarrativeStep(allSteps, step, idx, n) { - const { x, y } = this.projectPoint([step.latitude, step.longitude]); - const step2 = allSteps[idx + 1]; + const { x, y } = this.projectPoint([step.latitude, step.longitude]) + const step2 = allSteps[idx + 1] // don't draw if one of the steps has no location if (this.hasNoLocation(step) || this.hasNoLocation(step2)) return null - const p2 = this.projectPoint([step2.latitude, step2.longitude]); + const p2 = this.projectPoint([step2.latitude, step2.longitude]) return ( - ); + ) } renderNarrative(n) { - // TODO: representation for narrative lines - // const steps = n.steps.slice(0, n.steps.length - 1); - // - // return ( - // - // {steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))} - // - // ) - return null + const steps = n.steps.slice(0, n.steps.length - 1) + + return ( + + {steps.map((s, idx) => this.renderNarrativeStep(n.steps, s, idx, n))} + + ) } render() { - if (this.props.narrative === null) return (
); + if (this.props.narrative === null) return (
) return ( {this.props.narratives.map(n => this.renderNarrative(n))} - ); + ) } } -export default MapNarratives; +export default MapNarratives From cfea5ee49074d851398e1cd55a89e07e24b804a9 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 17:33:03 +0000 Subject: [PATCH 07/10] move card to left in narrative mode --- src/components/CardStack.jsx | 11 +++++++++-- src/components/Dashboard.jsx | 1 + src/scss/cardstack.scss | 11 ++++++++++- src/scss/narrativecard.scss | 5 +++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index 86d4b0e..6c04976 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -65,9 +65,16 @@ class CardStack extends React.Component { } render() { - if (this.props.selected.length > 0) { + const { isCardstack, isNarrative, selected } = this.props + if (selected.length > 0) { return ( -
+
{this.renderCardStackHeader()} {this.renderCardStackContent()}
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 9986768..370de79 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -78,6 +78,7 @@ class Dashboard extends React.Component { this.handleSelect([ narrative.steps[0] ]) this.props.actions.updateNarrative(narrative) } + moveInNarrative(amt) { const { current } = this.props.app.narrativeState const { narrative } = this.props.app diff --git a/src/scss/cardstack.scss b/src/scss/cardstack.scss index 1879969..90f3e77 100644 --- a/src/scss/cardstack.scss +++ b/src/scss/cardstack.scss @@ -1,7 +1,9 @@ @import 'burger'; @import 'card'; -$card-width: 500px; +$card-width: 370px; +$narrative-info-max-height: 250px; +$timeline-height: 170px; .card-stack { position: absolute; @@ -15,6 +17,13 @@ $card-width: 500px; color: white; -webkit-font-smoothing: antialiased; + &.narrative-mode { + right: auto; + left: 10px; + top: $narrative-info-max-height; + height: calc(100% - #{$narrative-info-max-height} - #{$timeline-height}); + } + &.full-height { max-height: calc(100% - 20px); } diff --git a/src/scss/narrativecard.scss b/src/scss/narrativecard.scss index 2987204..863b070 100644 --- a/src/scss/narrativecard.scss +++ b/src/scss/narrativecard.scss @@ -1,3 +1,5 @@ +$narrative-info-width: 370px; + /* NARRATIVE INFO */ @@ -6,10 +8,9 @@ NARRATIVE INFO top: 10px; left: 10px; // height: auto; - min-height: 500px; height: auto; max-height: 500px; - width: 370px; + width: $narrative-info-width; box-sizing: border-box; padding: 15px; max-height: calc(100% - 250px); From 8749b3ca348942f8877258cff328232fe3ca9891 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 18:02:50 +0000 Subject: [PATCH 08/10] WIP: render all cards in narrative --- src/components/Card.jsx | 60 +++++++++--------- src/components/CardStack.jsx | 114 ++++++++++++++++++++++------------- src/components/Dashboard.jsx | 1 - src/scss/cardstack.scss | 4 +- src/scss/narrativecard.scss | 3 +- 5 files changed, 104 insertions(+), 78 deletions(-) diff --git a/src/components/Card.jsx b/src/components/Card.jsx index ee269d5..7829616 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -1,28 +1,28 @@ -import copy from '../js/data/copy.json'; +import copy from '../js/data/copy.json' import { isNotNullNorUndefined, parseDate, formatterWithYear -} from '../js/utilities'; -import React from 'react'; +} from '../js/utilities' +import React from 'react' -import Spinner from './presentational/Spinner'; -import CardTimestamp from './presentational/CardTimestamp'; -import CardLocation from './presentational/CardLocation'; -import CardCaret from './presentational/CardCaret'; -import CardTags from './presentational/CardTags'; -import CardSummary from './presentational/CardSummary'; -import CardSource from './presentational/CardSource'; -import CardCategory from './presentational/CardCategory'; -import CardNarrative from './presentational/CardNarrative'; +import Spinner from './presentational/Spinner' +import CardTimestamp from './presentational/CardTimestamp' +import CardLocation from './presentational/CardLocation' +import CardCaret from './presentational/CardCaret' +import CardTags from './presentational/CardTags' +import CardSummary from './presentational/CardSummary' +import CardSource from './presentational/CardSource' +import CardCategory from './presentational/CardCategory' +import CardNarrative from './presentational/CardNarrative' class Card extends React.Component { constructor(props) { - super(props); + super(props) this.state = { isHighlighted: false - }; + } } toggle() { @@ -30,24 +30,24 @@ class Card extends React.Component { isHighlighted: !this.state.isHighlighted }, () => { if (!this.state.isHighlighted) { - this.props.onHighlight(this.props.event); + this.props.onHighlight(this.props.event) } else { - this.props.onHighlight(null); + this.props.onHighlight(null) } - }); + }) } makeTimelabel(timestamp) { - if (timestamp === null) return null; - const parsedTimestamp = parseDate(timestamp); - const timelabel = formatterWithYear(parsedTimestamp); - return timelabel; + if (timestamp === null) return null + const parsedTimestamp = parseDate(timestamp) + const timelabel = formatterWithYear(parsedTimestamp) + return timelabel } renderCategory() { - const categoryTitle = copy[this.props.language].cardstack.category; - const categoryLabel = this.props.event.category; - const color = this.props.getCategoryColor(this.props.event.category); + const categoryTitle = copy[this.props.language].cardstack.category + const categoryLabel = this.props.event.category + const color = this.props.getCategoryColor(this.props.event.category) return ( - ); + ) } renderSummary() { @@ -117,11 +117,11 @@ class Card extends React.Component { language={this.props.language} timestamp={this.props.event.timestamp} /> - ); + ) } renderNarrative() { - const links = this.props.getNarrativeLinks(this.props.event); + const links = this.props.getNarrativeLinks(this.props.event) if (links !== null) { @@ -147,7 +147,7 @@ class Card extends React.Component {
{this.renderSummary()}
- ); + ) } renderContent() { @@ -180,8 +180,8 @@ class Card extends React.Component { {this.renderContent()} {this.renderCaret()} - ); + ) } } -export default Card; +export default Card diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index 6c04976..f25e68a 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -1,44 +1,47 @@ -import React from 'react'; +import React from 'react' import { connect } from 'react-redux' import * as selectors from '../selectors' -import Card from './Card.jsx'; -import copy from '../js/data/copy.json'; +import Card from './Card.jsx' +import copy from '../js/data/copy.json' import { isNotNullNorUndefined -} from '../js/utilities.js'; +} from '../js/utilities.js' class CardStack extends React.Component { - - constructor(props) { - super(props); + renderCards(events) { + return events.map(event => ( + + )) } - renderCards() { - if (this.props.selected.length > 0) { - return this.props.selected.map((event) => { - return ( - - ); - }); + renderSelectedCards() { + const { selected } = this.props + if (selected.length > 0) { + return this.renderCards(selected) } - return ''; + return null + } + + renderNarrativeCards() { + const { narrative } = this.props + return this.renderCards(narrative.steps) } renderCardStackHeader() { - const header_lang = copy[this.props.language].cardstack.header; + const header_lang = copy[this.props.language].cardstack.header return (
    - {this.renderCards()} + {this.renderSelectedCards()}
- ); + ) + } + + renderNarrativeContent() { + return ( +
+
    + {this.renderNarrativeCards()} +
+
+ ) } render() { - const { isCardstack, isNarrative, selected } = this.props + const { isCardstack, selected, narrative } = this.props + if (selected.length > 0) { - return ( -
- {this.renderCardStackHeader()} - {this.renderCardStackContent()} -
- ); + } + > + {this.renderCardStackHeader()} + {this.renderCardStackContent()} +
+ ) + } else { + return ( +
+ {this.renderNarrativeContent()} +
+ ) + } } - return
; + + return
} } function mapStateToProps(state) { return { + narrative: selectors.selectActiveNarrative(state), selected: selectors.selectSelected(state), sourceError: state.app.errors.source, language: state.app.language, diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 370de79..abc68c2 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -127,7 +127,6 @@ class Dashboard extends React.Component { }} /> Date: Fri, 4 Jan 2019 10:26:02 +0000 Subject: [PATCH 09/10] display narrative events only in narrative mode; make card more minimal --- src/assets/transparent.png | Bin 0 -> 36334 bytes src/components/Card.jsx | 67 ++++++++---------- src/components/CardStack.jsx | 15 +++- src/components/Timeline.jsx | 13 ++-- .../presentational/TimelineHeader.js | 10 +-- src/js/utilities.js | 23 ++++++ src/scss/card.scss | 21 ++++-- src/scss/cardstack.scss | 5 +- src/scss/timeline.scss | 5 +- src/selectors/index.js | 29 +++----- 10 files changed, 110 insertions(+), 78 deletions(-) create mode 100644 src/assets/transparent.png diff --git a/src/assets/transparent.png b/src/assets/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..31726f4ef747c7bd5ffbb67d67232c1436551918 GIT binary patch literal 36334 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 literal 0 HcmV?d00001 diff --git a/src/components/Card.jsx b/src/components/Card.jsx index 7829616..2e996e9 100644 --- a/src/components/Card.jsx +++ b/src/components/Card.jsx @@ -21,19 +21,13 @@ class Card extends React.Component { constructor(props) { super(props) this.state = { - isHighlighted: false + isOpen: false } } toggle() { this.setState({ - isHighlighted: !this.state.isHighlighted - }, () => { - if (!this.state.isHighlighted) { - this.props.onHighlight(this.props.event) - } else { - this.props.onHighlight(null) - } + isOpen: !this.state.isOpen }) } @@ -49,13 +43,14 @@ class Card extends React.Component { const categoryLabel = this.props.event.category const color = this.props.getCategoryColor(this.props.event.category) - return ( - - ) + return null + // return ( + // + // ) } renderSummary() { @@ -63,7 +58,7 @@ class Card extends React.Component { ) } @@ -96,7 +91,7 @@ class Card extends React.Component { const source_lang = copy[this.props.language].cardstack.sources return ( -
+

{source_lang}:

{this.props.event.sources.map(source => ( -
+
+
{this.renderTimestamp()} {this.renderLocation()}
{this.renderCategory()} -
{this.renderSummary()}
) } - renderContent() { - if (this.state.isHighlighted) { - return ( -
- {this.renderTags()} - {this.renderSources()} - {this.renderNarrative()} -
- ) - } else { - return
- } + renderExtra() { + return ( +
+ {this.renderTags()} + {this.renderSources()} + {this.renderNarrative()} +
+ ) } renderCaret() { return ( this.toggle()} - isHighlighted={this.state.isHighlighted} + isOpen={this.state.isOpen} /> ) } render() { + const { isSelected } = this.props return ( -
  • - {this.renderHeader()} - {this.renderContent()} - {this.renderCaret()} +
  • + {this.renderMain()} + {this.state.isOpen ? this.renderExtra() : null} + {isSelected ? this.renderCaret() : null}
  • ) } diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx index f25e68a..4d37399 100644 --- a/src/components/CardStack.jsx +++ b/src/components/CardStack.jsx @@ -9,13 +9,18 @@ import { } from '../js/utilities.js' class CardStack extends React.Component { - renderCards(events) { - return events.map(event => ( + renderCards(events, selections) { + // if no selections provided, select all + if (!selections) + selections = events.map(e => true) + + return events.map((event, idx) => ( (idx === 0)) + + return this.renderCards(showing, selections) } renderCardStackHeader() { diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index ae42fb2..28aba21 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -34,18 +34,20 @@ class Timeline extends React.Component { } render() { + const { isNarrative, app, ui } = this.props let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`; - classes += (this.props.app.narrative !== null) ? ' narrative-mode' : ''; + classes += (app.narrative !== null) ? ' narrative-mode' : ''; return (
    { this.onClickArrow(); }} + hideInfo={isNarrative} />
    -
    +
    ); @@ -54,6 +56,7 @@ class Timeline extends React.Component { function mapStateToProps(state) { return { + isNarrative: !!state.app.narrative, domain: { events: state.domain.events, categories: selectors.selectCategories(state), diff --git a/src/components/presentational/TimelineHeader.js b/src/components/presentational/TimelineHeader.js index d75a605..f9ece09 100644 --- a/src/components/presentational/TimelineHeader.js +++ b/src/components/presentational/TimelineHeader.js @@ -1,11 +1,11 @@ import React from 'react'; -const TimelineHeader = ({ title, date0, date1, onClick }) => ( -
    -
    onClick()}> -

    +const TimelineHeader = ({ title, date0, date1, onClick, hideInfo }) => ( +
    +
    onClick()}> +

    -
    +

    {title}

    {date0} - {date1}

    diff --git a/src/js/utilities.js b/src/js/utilities.js index 751a93d..924e1f5 100644 --- a/src/js/utilities.js +++ b/src/js/utilities.js @@ -79,6 +79,29 @@ export function compareTimestamp (a, b) { return (parseTimestamp(a.timestamp) > parseTimestamp(b.timestamp)); } +/** + * Inset the full source represenation from 'allSources' into an event. The + * function is 'curried' to allow easy use with maps. To use for a single + * source, call with two sets of parentheses: + * const src = insetSourceFrom(sources)(anEvent) + */ +export function insetSourceFrom(allSources) { + return (event) => { + let sources + if (!event.sources) { + sources = [] + } else { + sources = event.sources.map(id => ( + allSources.hasOwnProperty(id) ? allSources[id] : null + )) + } + return { + ...event, + sources + } + } + +} /** * Debugging function: put in place of a mapStateToProps function to diff --git a/src/scss/card.scss b/src/scss/card.scss index 8979937..a801f0e 100644 --- a/src/scss/card.scss +++ b/src/scss/card.scss @@ -2,10 +2,10 @@ box-sizing: border-box; margin: 1px 0 0 0; padding: 15px; - border: 1px solid rgba(0, 0, 0, 0); - border-radius: 3px; + border: 1px solid $black; + // border-radius: 3px; transition: 0.2 ease; - background: $offwhite; + background: $darkwhite; color: $darkgrey; box-shadow: 0 19px 19px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22); font-size: $large; @@ -39,10 +39,13 @@ .card-row, .card-col { display: flex; flex-direction: row; - border-bottom: 1px solid $lightwhite; margin: 5px 0 10px 0; padding-bottom: 10px; + &.details { + border-bottom: 1px solid $lightwhite; + } + .card-cell { flex: 1; } @@ -120,6 +123,7 @@ height: 0; overflow: hidden; } + } .card-toggle p { @@ -197,6 +201,7 @@ .summary { overflow: auto; margin-top: 0; + border-bottom: none; } .tag { @@ -204,4 +209,12 @@ margin: 0; margin-right: 5px; } + + &.selected { + background: $offwhite; + } + + .card-row { + border-color: darkgray; + } } diff --git a/src/scss/cardstack.scss b/src/scss/cardstack.scss index da6ca92..0b83da3 100644 --- a/src/scss/cardstack.scss +++ b/src/scss/cardstack.scss @@ -11,17 +11,16 @@ $timeline-height: 170px; right: 10px; max-height: calc(100% - 208px); height: auto; - overflow: auto; + overflow: hidden; box-shadow: 0 19px 38px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22); z-index: $header; color: white; - -webkit-font-smoothing: antialiased; &.narrative-mode { right: auto; left: 10px; top: $narrative-info-max-height + 12px; - height: calc(100% - #{$narrative-info-max-height} - #{$timeline-height}); + height: calc(100% - #{$narrative-info-max-height} - #{$timeline-height} - 12px); } &.full-height { diff --git a/src/scss/timeline.scss b/src/scss/timeline.scss index 2205ce1..24666a4 100644 --- a/src/scss/timeline.scss +++ b/src/scss/timeline.scss @@ -1,4 +1,3 @@ - .timeline-wrapper { position: fixed; box-sizing: border-box; @@ -67,6 +66,10 @@ } .timeline-info { + &.hidden { + display: none; + } + width: calc(#{$card-width} - 20px); position: absolute; margin-top: -70px; margin-left: 10px; diff --git a/src/selectors/index.js b/src/selectors/index.js index 76cb8ad..d560a00 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -1,5 +1,5 @@ import { createSelector} from 'reselect' -import { parseTimestamp, compareTimestamp } from '../js/utilities' +import { parseTimestamp, compareTimestamp, insetSourceFrom } from '../js/utilities' // Input selectors export const getEvents = state => state.domain.events @@ -22,6 +22,8 @@ export const getTagTree = state => state.domain.tags export const getTagsFilter = state => state.app.filters.tags export const getTimeRange = state => state.app.filters.timerange + + /** * Some handy helpers */ @@ -90,8 +92,8 @@ export const selectEvents = createSelector( * and if TAGS are being used, select them if their tags are enabled */ export const selectNarratives = createSelector( - [getEvents, getNarratives, getTagsFilter, getTimeRange], - (events, narrativesMeta, tagFilters, timeRange) => { + [getEvents, getNarratives, getTagsFilter, getTimeRange, getSources], + (events, narrativesMeta, tagFilters, timeRange, sources) => { const narratives = {} const narrativeSkeleton = id => ({ id, steps: [] }) @@ -109,7 +111,8 @@ export const selectNarratives = createSelector( // add evt to steps if (isInNarrative) - narratives[narrative].steps.push(evt) + // NB: insetSourceFrom is a 'curried' function to allow with maps + narratives[narrative].steps.push(insetSourceFrom(sources)(evt)) }) }) @@ -173,6 +176,8 @@ export const selectLocations = createSelector( } ) + + /** * Of all the sources, select those that are relevant to the selected events. */ @@ -183,21 +188,7 @@ export const selectSelected = createSelector( return [] } - // NB: return source object if exists, otherwise null - const srcs = selected - .map(e => e.sources) - .map(_sources => { - if (!_sources) return [] - return _sources.map(id => ( - sources.hasOwnProperty(id) ? sources[id] : null - )) - } - ) - - return selected.map((s, idx) => ({ - ...s, - sources: srcs[idx] - })) + return selected.map(insetSourceFrom(sources)) } ) From a4781815ccd6c1f7475a9a4b72e69b258a4082f4 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 4 Jan 2019 10:26:25 +0000 Subject: [PATCH 10/10] rm transparent.png --- src/assets/transparent.png | Bin 36334 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/assets/transparent.png diff --git a/src/assets/transparent.png b/src/assets/transparent.png deleted file mode 100644 index 31726f4ef747c7bd5ffbb67d67232c1436551918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36334 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