lint jsx files

This commit is contained in:
Lachlan Kermode
2019-01-23 11:35:34 +00:00
parent 939a4833b8
commit fb4d0c2d86
28 changed files with 572 additions and 627 deletions

View File

@@ -9,7 +9,7 @@
"build": "NODE_ENV=production webpack --mode production", "build": "NODE_ENV=production webpack --mode production",
"test": "ava --verbose", "test": "ava --verbose",
"test-watch": "ava --watch", "test-watch": "ava --watch",
"lint": "standard \"src/**/*.js\" \"test/**/*.js\"" "lint": "standard \"src/**/*.js\" \"src/**/*.jsx\" \"test/**/*.js\""
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",

View File

@@ -1,16 +1,15 @@
import '../scss/main.scss'; import '../scss/main.scss'
import React from 'react'; import React from 'react'
import Dashboard from './Dashboard.jsx'; import Dashboard from './Dashboard.jsx'
class App extends React.Component { class App extends React.Component {
render () {
render() {
return ( return (
<div> <div>
<Dashboard /> <Dashboard />
</div> </div>
); )
} }
} }
export default App; export default App

View File

@@ -1,59 +1,40 @@
import copy from '../js/data/copy.json' import copy from '../js/data/copy.json'
import { import {
isNotNullNorUndefined,
parseDate, parseDate,
formatterWithYear formatterWithYear
} from '../js/utilities' } from '../js/utilities'
import React from 'react' import React from 'react'
import Spinner from './presentational/Spinner'
import CardTimestamp from './presentational/Card/Timestamp' import CardTimestamp from './presentational/Card/Timestamp'
import CardLocation from './presentational/Card/Location' import CardLocation from './presentational/Card/Location'
import CardCaret from './presentational/Card/Caret' import CardCaret from './presentational/Card/Caret'
import CardTags from './presentational/Card/Tags' import CardTags from './presentational/Card/Tags'
import CardSummary from './presentational/Card/Summary' import CardSummary from './presentational/Card/Summary'
import CardSource from './presentational/Card/Source' import CardSource from './presentational/Card/Source'
import CardCategory from './presentational/Card/Category'
import CardNarrative from './presentational/Card/Narrative' import CardNarrative from './presentational/Card/Narrative'
class Card extends React.Component { class Card extends React.Component {
constructor (props) {
constructor(props) {
super(props) super(props)
this.state = { this.state = {
isOpen: false isOpen: false
} }
} }
toggle() { toggle () {
this.setState({ this.setState({
isOpen: !this.state.isOpen isOpen: !this.state.isOpen
}) })
} }
makeTimelabel(timestamp) { makeTimelabel (timestamp) {
if (timestamp === null) return null if (timestamp === null) return null
const parsedTimestamp = parseDate(timestamp) const parsedTimestamp = parseDate(timestamp)
const timelabel = formatterWithYear(parsedTimestamp) const timelabel = formatterWithYear(parsedTimestamp)
return timelabel return timelabel
} }
renderCategory() { renderSummary () {
const categoryTitle = copy[this.props.language].cardstack.category
const categoryLabel = this.props.event.category
const color = this.props.getCategoryColor(this.props.event.category)
return null
// return (
// <CardCategory
// categoryTitle={categoryTitle}
// categoryLabel={categoryLabel}
// color={color}
// />
// )
}
renderSummary() {
return ( return (
<CardSummary <CardSummary
language={this.props.language} language={this.props.language}
@@ -63,7 +44,7 @@ class Card extends React.Component {
) )
} }
renderTags() { renderTags () {
if (!this.props.tags || (this.props.tags && this.props.tags.length === 0)) { if (!this.props.tags || (this.props.tags && this.props.tags.length === 0)) {
return null return null
} }
@@ -75,7 +56,7 @@ class Card extends React.Component {
) )
} }
renderLocation() { renderLocation () {
return ( return (
<CardLocation <CardLocation
language={this.props.language} language={this.props.language}
@@ -84,15 +65,15 @@ class Card extends React.Component {
) )
} }
renderSources() { renderSources () {
if (this.props.sourceError) { if (this.props.sourceError) {
return <div>ERROR: something went wrong loading sources, TODO:</div> return <div>ERROR: something went wrong loading sources, TODO:</div>
} }
const source_lang = copy[this.props.language].cardstack.sources const sourceLang = copy[this.props.language].cardstack.sources
return ( return (
<div className='card-col'> <div className='card-col'>
<h4>{source_lang}: </h4> <h4>{sourceLang}: </h4>
{this.props.event.sources.map(source => ( {this.props.event.sources.map(source => (
<CardSource <CardSource
isLoading={this.props.isLoading} isLoading={this.props.isLoading}
@@ -105,7 +86,7 @@ class Card extends React.Component {
} }
// NB: should be internaionalized. // NB: should be internaionalized.
renderTimestamp() { renderTimestamp () {
return ( return (
<CardTimestamp <CardTimestamp
makeTimelabel={(timestamp) => this.makeTimelabel(timestamp)} makeTimelabel={(timestamp) => this.makeTimelabel(timestamp)}
@@ -115,11 +96,10 @@ class Card extends React.Component {
) )
} }
renderNarrative() { renderNarrative () {
const links = this.props.getNarrativeLinks(this.props.event) const links = this.props.getNarrativeLinks(this.props.event)
if (links !== null) { if (links !== null) {
return ( return (
<CardNarrative <CardNarrative
select={(event) => this.props.onSelect([event])} select={(event) => this.props.onSelect([event])}
@@ -131,7 +111,7 @@ class Card extends React.Component {
} }
} }
renderMain() { renderMain () {
return ( return (
<div className='card-container'> <div className='card-container'>
<div className='card-row details'> <div className='card-row details'>
@@ -144,7 +124,7 @@ class Card extends React.Component {
) )
} }
renderExtra() { renderExtra () {
return ( return (
<div className='card-bottomhalf'> <div className='card-bottomhalf'>
{this.renderTags()} {this.renderTags()}
@@ -154,7 +134,7 @@ class Card extends React.Component {
) )
} }
renderCaret() { renderCaret () {
return ( return (
<CardCaret <CardCaret
toggle={() => this.toggle()} toggle={() => this.toggle()}
@@ -163,10 +143,10 @@ class Card extends React.Component {
) )
} }
render() { render () {
const { isSelected } = this.props const { isSelected } = this.props
return ( return (
<li className={`event-card ${isSelected ? 'selected' : ''}`}> <li className={`event-card ${isSelected ? 'selected' : ''}`}>
{this.renderMain()} {this.renderMain()}
{this.state.isOpen ? this.renderExtra() : null} {this.state.isOpen ? this.renderExtra() : null}
{isSelected ? this.renderCaret() : null} {isSelected ? this.renderCaret() : null}

View File

@@ -4,15 +4,11 @@ import * as selectors from '../selectors'
import Card from './Card.jsx' import Card from './Card.jsx'
import copy from '../js/data/copy.json' import copy from '../js/data/copy.json'
import {
isNotNullNorUndefined
} from '../js/utilities.js'
class CardStack extends React.Component { class CardStack extends React.Component {
renderCards(events, selections) { renderCards (events, selections) {
// if no selections provided, select all // if no selections provided, select all
if (!selections) if (!selections) { selections = events.map(e => true) }
selections = events.map(e => true)
return events.map((event, idx) => ( return events.map((event, idx) => (
<Card <Card
@@ -32,7 +28,7 @@ class CardStack extends React.Component {
)) ))
} }
renderSelectedCards() { renderSelectedCards () {
const { selected } = this.props const { selected } = this.props
if (selected.length > 0) { if (selected.length > 0) {
return this.renderCards(selected) return this.renderCards(selected)
@@ -40,7 +36,7 @@ class CardStack extends React.Component {
return null return null
} }
renderNarrativeCards() { renderNarrativeCards () {
const { narrative } = this.props const { narrative } = this.props
const showing = narrative.steps.slice(narrative.current) const showing = narrative.steps.slice(narrative.current)
const selections = showing const selections = showing
@@ -49,8 +45,8 @@ class CardStack extends React.Component {
return this.renderCards(showing, selections) return this.renderCards(showing, selections)
} }
renderCardStackHeader() { renderCardStackHeader () {
const header_lang = copy[this.props.language].cardstack.header const headerLang = copy[this.props.language].cardstack.header
return ( return (
<div <div
@@ -58,17 +54,17 @@ class CardStack extends React.Component {
className='card-stack-header' className='card-stack-header'
onClick={() => this.props.onToggleCardstack()} onClick={() => this.props.onToggleCardstack()}
> >
<button className="side-menu-burg is-active"><span></span></button> <button className='side-menu-burg is-active'><span /></button>
<p className="header-copy top"> <p className='header-copy top'>
{`${this.props.selected.length} ${header_lang}`} {`${this.props.selected.length} ${headerLang}`}
</p> </p>
</div> </div>
) )
} }
renderCardStackContent() { renderCardStackContent () {
return ( return (
<div id="card-stack-content" className="card-stack-content"> <div id='card-stack-content' className='card-stack-content'>
<ul> <ul>
{this.renderSelectedCards()} {this.renderSelectedCards()}
</ul> </ul>
@@ -76,9 +72,9 @@ class CardStack extends React.Component {
) )
} }
renderNarrativeContent() { renderNarrativeContent () {
return ( return (
<div id="card-stack-content" className="card-stack-content"> <div id='card-stack-content' className='card-stack-content'>
<ul> <ul>
{this.renderNarrativeCards()} {this.renderNarrativeCards()}
</ul> </ul>
@@ -86,14 +82,14 @@ class CardStack extends React.Component {
) )
} }
render() { render () {
const { isCardstack, selected, narrative } = this.props const { isCardstack, selected, narrative } = this.props
if (selected.length > 0) { if (selected.length > 0) {
if (!narrative) { if (!narrative) {
return ( return (
<div <div
id="card-stack" id='card-stack'
className={`card-stack className={`card-stack
${isCardstack ? '' : ' folded'}` ${isCardstack ? '' : ' folded'}`
} }
@@ -105,7 +101,7 @@ class CardStack extends React.Component {
} else { } else {
return ( return (
<div <div
id="card-stack" id='card-stack'
className={`card-stack narrative-mode className={`card-stack narrative-mode
${isCardstack ? '' : ' folded'}` ${isCardstack ? '' : ' folded'}`
} }
@@ -116,11 +112,11 @@ class CardStack extends React.Component {
} }
} }
return <div/> return <div />
} }
} }
function mapStateToProps(state) { function mapStateToProps (state) {
return { return {
narrative: selectors.selectActiveNarrative(state), narrative: selectors.selectActiveNarrative(state),
selected: selectors.selectSelected(state), selected: selectors.selectSelected(state),

View File

@@ -14,10 +14,10 @@ import InfoPopUp from './InfoPopup.jsx'
import Timeline from './Timeline.jsx' import Timeline from './Timeline.jsx'
import Notification from './Notification.jsx' import Notification from './Notification.jsx'
import { parseDate, injectSource } from '../js/utilities' import { parseDate } from '../js/utilities'
class Dashboard extends React.Component { class Dashboard extends React.Component {
constructor(props) { constructor (props) {
super(props) super(props)
this.handleViewSource = this.handleViewSource.bind(this) this.handleViewSource = this.handleViewSource.bind(this)
@@ -30,28 +30,28 @@ class Dashboard extends React.Component {
this.eventsById = {} this.eventsById = {}
} }
componentDidMount() { componentDidMount () {
if (!this.props.app.isMobile) { if (!this.props.app.isMobile) {
this.props.actions.fetchDomain() this.props.actions.fetchDomain()
.then(domain => this.props.actions.updateDomain(domain)) .then(domain => this.props.actions.updateDomain(domain))
} }
} }
handleHighlight(highlighted) { handleHighlight (highlighted) {
this.props.actions.updateHighlighted((highlighted) ? highlighted : null) this.props.actions.updateHighlighted((highlighted) || null)
} }
getEventById(eventId) { getEventById (eventId) {
if (this.eventsById[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) this.eventsById[eventId] = this.props.domain.events.find(ev => ev.id === eventId)
return this.eventsById[eventId] return this.eventsById[eventId]
} }
handleViewSource(source) { handleViewSource (source) {
this.props.actions.updateSource(source) this.props.actions.updateSource(source)
} }
handleSelect(selected) { handleSelect (selected) {
if (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)) eventsToSelect = eventsToSelect.sort((a, b) => parseDate(a.timestamp) - parseDate(b.timestamp))
@@ -60,24 +60,23 @@ class Dashboard extends React.Component {
} }
} }
getCategoryColor(category) { getCategoryColor (category) {
return this.props.ui.style.categories[category] || this.props.ui.style.categories['default'] return this.props.ui.style.categories[category] || this.props.ui.style.categories['default']
} }
getNarrativeLinks(event) { getNarrativeLinks (event) {
const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative) const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative)
if (narrative) return narrative.byId[event.id] if (narrative) return narrative.byId[event.id]
return null return null
} }
setNarrative(narrative) { setNarrative (narrative) {
// only handleSelect if narrative is not null // only handleSelect if narrative is not null
if (!!narrative) if (narrative) { this.handleSelect([ narrative.steps[0] ]) }
this.handleSelect([ narrative.steps[0] ])
this.props.actions.updateNarrative(narrative) this.props.actions.updateNarrative(narrative)
} }
moveInNarrative(amt) { moveInNarrative (amt) {
const { current } = this.props.app.narrativeState const { current } = this.props.app.narrativeState
const { narrative } = this.props.app const { narrative } = this.props.app
@@ -91,7 +90,7 @@ class Dashboard extends React.Component {
} }
} }
render() { render () {
const { actions, app, domain, ui } = this.props const { actions, app, domain, ui } = this.props
return ( return (
<div> <div>
@@ -107,7 +106,7 @@ class Dashboard extends React.Component {
methods={{ methods={{
onSelect: this.handleSelect, onSelect: this.handleSelect,
onSelectNarrative: this.setNarrative, onSelectNarrative: this.setNarrative,
getCategoryColor: this.getCategoryColor, getCategoryColor: this.getCategoryColor
}} }}
/> />
<Timeline <Timeline
@@ -126,7 +125,7 @@ class Dashboard extends React.Component {
getCategoryColor={category => this.getCategoryColor(category)} getCategoryColor={category => this.getCategoryColor(category)}
/> />
<NarrativeControls <NarrativeControls
narrative={!!app.narrative ? { narrative={app.narrative ? {
...app.narrative, ...app.narrative,
current: app.narrativeState.current current: app.narrativeState.current
} : null} } : null}
@@ -150,7 +149,8 @@ class Dashboard extends React.Component {
<SourceOverlay <SourceOverlay
source={app.source} source={app.source}
onCancel={() => { onCancel={() => {
actions.updateSource(null)} actions.updateSource(null)
}
} }
/> />
) : null} ) : null}
@@ -163,7 +163,7 @@ class Dashboard extends React.Component {
} }
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps (dispatch) {
return { return {
actions: bindActionCreators(actions, dispatch) actions: bindActionCreators(actions, dispatch)
} }
@@ -172,5 +172,5 @@ function mapDispatchToProps(dispatch) {
export default connect( export default connect(
state => state, state => state,
// state => injectSource("Youtube - Novodvirske Tank Separatist Patrol Video"), // state => injectSource("Youtube - Novodvirske Tank Separatist Patrol Video"),
mapDispatchToProps, mapDispatchToProps
)(Dashboard) )(Dashboard)

View File

@@ -1,32 +1,32 @@
import React from 'react'; import React from 'react'
const Icon = ({ iconType }) => { const Icon = ({ iconType }) => {
if (iconType === 'personas') { if (iconType === 'personas') {
return ( return (
<svg x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<path d="M15.464,17.713" /> <path d='M15.464,17.713' />
<path d="M5.526,17.713c-1.537,0.595-3,1.472-4.314,2.637l1.114,17.081h16.338" /> <path d='M5.526,17.713c-1.537,0.595-3,1.472-4.314,2.637l1.114,17.081h16.338' />
<path d="M12.283,15.522c-1.707,0.661-3.332,1.636-4.792,2.93l1.238,18.979h18.153" /> <path d='M12.283,15.522c-1.707,0.661-3.332,1.636-4.792,2.93l1.238,18.979h18.153' />
<circle cx="27.432" cy="8.876" r="6.877" /> <circle cx='27.432' cy='8.876' r='6.877' />
<path d="M21.297,13.088c-1.896,0.733-3.702,1.817-5.326,3.256l1.375,21.087h20.17l1.376-21.087c-1.624-1.438-3.43-2.522-5.326-3.256" /> <path d='M21.297,13.088c-1.896,0.733-3.702,1.817-5.326,3.256l1.375,21.087h20.17l1.376-21.087c-1.624-1.438-3.43-2.522-5.326-3.256' />
<path d="M20.968,6.547c-0.926-0.554-2.006-0.877-3.163-0.877c-3.418,0-6.188,2.771-6.188,6.188c0,2.811,1.875,5.18,4.441,5.935" /> <path d='M20.968,6.547c-0.926-0.554-2.006-0.877-3.163-0.877c-3.418,0-6.188,2.771-6.188,6.188c0,2.811,1.875,5.18,4.441,5.935' />
<path d="M12.38,8.881c-0.738-0.361-1.564-0.57-2.441-0.57c-3.076,0-5.57,2.494-5.57,5.57c0,1.983,1.04,3.72,2.601,4.707" /> <path d='M12.38,8.881c-0.738-0.361-1.564-0.57-2.441-0.57c-3.076,0-5.57,2.494-5.57,5.57c0,1.983,1.04,3.72,2.601,4.707' />
</svg> </svg>
); )
} else if (iconType === 'tipos') { } else if (iconType === 'tipos') {
return ( return (
<svg x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<path strokeDasharray="3, 4" d="M22.326,5.346 <path strokeDasharray='3, 4' d='M22.326,5.346
c-2.154-2.081-5.082-3.367-8.314-3.367c-6.614,0-11.976,5.361-11.976,11.974c0,6.613,5.361,11.977,11.976,11.977 c-2.154-2.081-5.082-3.367-8.314-3.367c-6.614,0-11.976,5.361-11.976,11.974c0,6.613,5.361,11.977,11.976,11.977
c0.228,0,0.449-0.021,0.674-0.034"/> c0.228,0,0.449-0.021,0.674-0.034' />
<circle cx="23" cy="17.288" r="11.975"/> <circle cx='23' cy='17.288' r='11.975' />
<circle strokeDasharray="3, 4" cx="25.987" cy="26.926" r="11.976" /> <circle strokeDasharray='3, 4' cx='25.987' cy='26.926' r='11.976' />
</svg> </svg>
); )
} else if (iconType === 'hardware') { } else if (iconType === 'hardware') {
return ( return (
<svg x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<path d="M20,1.695C12.571,1.696,6.286,2.019,5.272,2.452C5.253,2.458,5.233,2.466,5.215,2.474 <path d='M20,1.695C12.571,1.696,6.286,2.019,5.272,2.452C5.253,2.458,5.233,2.466,5.215,2.474
c-0.01,0.004-0.019,0.008-0.027,0.012C4.38,2.831,3.803,4.256,3.802,5.907v3.502H2.926H1.175c-0.241,0-0.438,0.196-0.438,0.438 c-0.01,0.004-0.019,0.008-0.027,0.012C4.38,2.831,3.803,4.256,3.802,5.907v3.502H2.926H1.175c-0.241,0-0.438,0.196-0.438,0.438
v0.875v5.254c0,0.242,0.196,0.438,0.438,0.438h1.751c0.242,0,0.438-0.195,0.438-0.438V11.16h0.438v15.324h5.691 v0.875v5.254c0,0.242,0.196,0.438,0.438,0.438h1.751c0.242,0,0.438-0.195,0.438-0.438V11.16h0.438v15.324h5.691
c0.242,0,0.438,0.195,0.438,0.438v1.751c0,0.241-0.195,0.438-0.438,0.438H3.802v3.063c0,0.626,0.167,1.203,0.438,1.515v3.74 c0.242,0,0.438,0.195,0.438,0.438v1.751c0,0.241-0.195,0.438-0.438,0.438H3.802v3.063c0,0.626,0.167,1.203,0.438,1.515v3.74
@@ -38,46 +38,46 @@ const Icon = ({ iconType }) => {
V7.22c0,0.242-0.195,0.438-0.438,0.438H4.991c-0.242,0-0.438-0.196-0.438-0.438V5.469C4.553,4.261,4.945,3.28,5.429,3.28z V7.22c0,0.242-0.195,0.438-0.438,0.438H4.991c-0.242,0-0.438-0.196-0.438-0.438V5.469C4.553,4.261,4.945,3.28,5.429,3.28z
M5.553,8.534h28.895c0.483,0,0.876,0.392,0.876,0.875v13.134c0,0.484-0.393,0.876-0.876,0.876h-3.466c0,0-0.863,0.613-0.912,0.613 M5.553,8.534h28.895c0.483,0,0.876,0.392,0.876,0.875v13.134c0,0.484-0.393,0.876-0.876,0.876h-3.466c0,0-0.863,0.613-0.912,0.613
H9.931c-0.113,0-0.225-0.022-0.33-0.065l-0.778-0.548h-3.27c-0.483,0-0.875-0.392-0.875-0.876V9.409 H9.931c-0.113,0-0.225-0.022-0.33-0.065l-0.778-0.548h-3.27c-0.483,0-0.875-0.392-0.875-0.876V9.409
C4.678,8.926,5.069,8.534,5.553,8.534L5.553,8.534z"/> C4.678,8.926,5.069,8.534,5.553,8.534L5.553,8.534z' />
</svg> </svg>
); )
} else if (iconType === 'escenas') { } else if (iconType === 'escenas') {
return ( return (
<svg className="scenes" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg className='scenes' x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<path d="M36.729,14.743v13.15l-14.225,6.693V21.438L36.729,14.743 M38.732,11.045L20.5,19.625v18.662l18.232-8.58V11.045 <path d='M36.729,14.743v13.15l-14.225,6.693V21.438L36.729,14.743 M38.732,11.045L20.5,19.625v18.662l18.232-8.58V11.045
L38.732,11.045z" /> L38.732,11.045z' />
<path d="M4.271,14.743l14.225,6.695v13.148L4.271,27.894V14.743 M2.268,11.045v18.662l18.232,8.58V19.625L2.268,11.045L2.268,11.045 <path d='M4.271,14.743l14.225,6.695v13.148L4.271,27.894V14.743 M2.268,11.045v18.662l18.232,8.58V19.625L2.268,11.045L2.268,11.045
z" /> z' />
<path d="M20.5,4.844l13.289,6.202L20.5,17.247L7.209,11.046L20.5,4.844 M20.5,2.537L2.268,11.045L20.5,19.554l18.232-8.509 <path d='M20.5,4.844l13.289,6.202L20.5,17.247L7.209,11.046L20.5,4.844 M20.5,2.537L2.268,11.045L20.5,19.554l18.232-8.509
L20.5,2.537L20.5,2.537z" /> L20.5,2.537L20.5,2.537z' />
</svg> </svg>
); )
} else if (iconType === 'docs') { } else if (iconType === 'docs') {
return ( return (
<svg x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<path d="M31.543,5.987V3.158 <path d='M31.543,5.987V3.158
c0-1.103-0.095-1.197-1.197-1.197H4.791c-1.103,0-1.198,0.095-1.198,1.197V32.84c0,1.103,0.095,1.197,1.198,1.197h2.829"/> c0-1.103-0.095-1.197-1.197-1.197H4.791c-1.103,0-1.198,0.095-1.198,1.197V32.84c0,1.103,0.095,1.197,1.198,1.197h2.829' />
<path d="M35.57,36.866 <path d='M35.57,36.866
c0,1.103-0.096,1.198-1.198,1.198H8.817c-1.103,0-1.198-0.096-1.198-1.198V7.185c0-1.103,0.095-1.197,1.198-1.197h25.555 c0,1.103-0.096,1.198-1.198,1.198H8.817c-1.103,0-1.198-0.096-1.198-1.198V7.185c0-1.103,0.095-1.197,1.198-1.197h25.555
c1.103,0,1.198,0.095,1.198,1.197V36.866z"/> c1.103,0,1.198,0.095,1.198,1.197V36.866z' />
<path d="M58.755,29.633"/> <path d='M58.755,29.633' />
<path d="M21.86,40.072"/> <path d='M21.86,40.072' />
<path d="M-22.755,58.555"/> <path d='M-22.755,58.555' />
<line x1="11.612" y1="11.977" x2="31.577" y2="11.977"/> <line x1='11.612' y1='11.977' x2='31.577' y2='11.977' />
<line x1="11.612" y1="17.966" x2="31.577" y2="17.966"/> <line x1='11.612' y1='17.966' x2='31.577' y2='17.966' />
<line x1="11.612" y1="29.945" x2="31.577" y2="29.945"/> <line x1='11.612' y1='29.945' x2='31.577' y2='29.945' />
<line x1="11.612" y1="23.955" x2="31.577" y2="23.955"/> <line x1='11.612' y1='23.955' x2='31.577' y2='23.955' />
</svg> </svg>
) )
} else if (iconType === 'search') { } else if (iconType === 'search') {
return ( return (
<svg x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enableBackground="new 0 0 40 40"> <svg x='0px' y='0px' width='40px' height='40px' viewBox='0 0 40 40' enableBackground='new 0 0 40 40'>
<circle cx="18.306" cy="18.307" r="13.856"/> <circle cx='18.306' cy='18.307' r='13.856' />
<path strokeLinecap="round" strokeLinejoin="round" d="M28.24,28.24 <path strokeLinecap='round' strokeLinejoin='round' d='M28.24,28.24
l8.346,8.346L28.24,28.24z"/> l8.346,8.346L28.24,28.24z' />
</svg> </svg>
); )
} }
} }
export default Icon; export default Icon

View File

@@ -1,22 +1,21 @@
import React from 'react'; import React from 'react'
import copy from '../js/data/copy.json'; import copy from '../js/data/copy.json'
// NB: should we make this componetn part of a future feature? // NB: should we make this componetn part of a future feature?
export default class InfoPopUp extends React.Component{ export default class InfoPopUp extends React.Component {
renderView2DCopy () {
renderView2DCopy() { return copy[this.props.app.language].legend.view2d.paragraphs.map(paragraph => <p>{paragraph}</p>)
return copy[this.props.app.language].legend.view2d.paragraphs.map(paragraph => <p>{paragraph}</p>);
} }
renderCategoryColors() { renderCategoryColors () {
const colors = copy[this.props.app.language].legend.view2d.colors.slice(0); const colors = copy[this.props.app.language].legend.view2d.colors.slice(0)
colors.reverse(); colors.reverse()
return ( return (
<div className="legend-labels" style={{ 'margin-left': '-10px' }}> <div className='legend-labels' style={{ 'margin-left': '-10px' }}>
{colors.map((color, idx) => { {colors.map((color, idx) => {
return ( return (
<div className="label" style={{ 'margin-left': `${idx*5}` }}> <div className='label' style={{ 'margin-left': `${idx * 5}` }}>
<div className={`color-category ${color.class}`}></div> <div className={`color-category ${color.class}`} />
{color.label} {color.label}
</div> </div>
) )
@@ -25,51 +24,51 @@ export default class InfoPopUp extends React.Component{
) )
} }
renderView2DLegend() { renderView2DLegend () {
return ( return (
<div className={`infopopup ${(this.props.app.flags.isInfopopup) ? '' : 'hidden'}`}> <div className={`infopopup ${(this.props.app.flags.isInfopopup) ? '' : 'hidden'}`}>
<button onClick={() => this.props.toggle()} className="side-menu-burg over-white is-active"><span /></button> <button onClick={() => this.props.toggle()} className='side-menu-burg over-white is-active'><span /></button>
{this.renderView2DCopy()} {this.renderView2DCopy()}
<div className="legend"> <div className='legend'>
<div className="legend-section" style={{ 'height': '100px' }}> <div className='legend-section' style={{ 'height': '100px' }}>
<svg x="0px" y="0px" width="100px" height="100px" viewBox="0 0 100 100" enableBackground="new 0 0 100 100"> <svg x='0px' y='0px' width='100px' height='100px' viewBox='0 0 100 100' enableBackground='new 0 0 100 100'>
<circle fill="#D2CD28" cx="50" cy="50" r="50"/> <circle fill='#D2CD28' cx='50' cy='50' r='50' />
<circle fill="#662770" cx="50" cy="50" r="40"/> <circle fill='#662770' cx='50' cy='50' r='40' />
<circle fill="#2F409A" cx="50" cy="50" r="30"/> <circle fill='#2F409A' cx='50' cy='50' r='30' />
<circle fill="#256C36" cx="50" cy="50" r="20"/> <circle fill='#256C36' cx='50' cy='50' r='20' />
<circle fill="#FF0000" cx="50" cy="50" r="10"/> <circle fill='#FF0000' cx='50' cy='50' r='10' />
</svg> </svg>
{this.renderCategoryColors()} {this.renderCategoryColors()}
</div> </div>
<div className="legend-section"> <div className='legend-section'>
<svg x="0px" y="0px" width="100px" height="30px" viewBox="0 0 100 30" enableBackground="new 0 0 100 30"> <svg x='0px' y='0px' width='100px' height='30px' viewBox='0 0 100 30' enableBackground='new 0 0 100 30'>
<line fill="none" stroke="#2F409A" strokeDasharray="4,4" x1="30" y1="15" x2="70" y2="15"/> <line fill='none' stroke='#2F409A' strokeDasharray='4,4' x1='30' y1='15' x2='70' y2='15' />
<circle fill="2F409A" fillOpacity="0.2" stroke="#2F409A" strokeDasharray="4,4" cx="80" cy="15" r="10"/> <circle fill='2F409A' fillOpacity='0.2' stroke='#2F409A' strokeDasharray='4,4' cx='80' cy='15' r='10' />
<circle fill="2F409A" fillOpacity="0.2" stroke="#2F409A" strokeDasharray="4,4" cx="20" cy="15" r="10"/> <circle fill='2F409A' fillOpacity='0.2' stroke='#2F409A' strokeDasharray='4,4' cx='20' cy='15' r='10' />
</svg> </svg>
<div className="legend-labels"> <div className='legend-labels'>
<div className="label">Comunicaciones</div> <div className='label'>Comunicaciones</div>
</div> </div>
</div> </div>
<div className="legend-section"> <div className='legend-section'>
<svg x="0px" y="0px" width="100px" height="30px" viewBox="0 0 100 30" enableBackground="new 0 0 100 30"> <svg x='0px' y='0px' width='100px' height='30px' viewBox='0 0 100 30' enableBackground='new 0 0 100 30'>
<circle opacity="0.3" fill="#FF0000" cx="50" cy="15" r="15"/> <circle opacity='0.3' fill='#FF0000' cx='50' cy='15' r='15' />
</svg> </svg>
<div className="legend-labels"> <div className='legend-labels'>
<div className="label">Ataques</div> <div className='label'>Ataques</div>
</div> </div>
</div> </div>
<div className="legend-section"> <div className='legend-section'>
<svg x="0px" y="0px" width="100px" height="30px" viewBox="0 40 100 30" enableBackground="new 0 0 100 70"> <svg x='0px' y='0px' width='100px' height='30px' viewBox='0 40 100 30' enableBackground='new 0 0 100 70'>
<polyline fill="none" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" stroke-miterlimit="10" points=" <polyline fill='none' stroke='#000000' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' stroke-miterlimit='10' points='
8.376,63.723 47.287,63.723 60,46 106,46 "/> 8.376,63.723 47.287,63.723 60,46 106,46 ' />
<line stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" x1="33.723" y1="59.663" x2="39.069" y2="63.723"/> <line stroke='#000000' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='33.723' y1='59.663' x2='39.069' y2='63.723' />
<line stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" x1="33.723" y1="67.782" x2="39.069" y2="63.723"/> <line stroke='#000000' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='33.723' y1='67.782' x2='39.069' y2='63.723' />
<line stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" x1="78.849" y1="41.94" x2="84.195" y2="46"/> <line stroke='#000000' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='78.849' y1='41.94' x2='84.195' y2='46' />
<line stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" x1="78.849" y1="50.06" x2="84.195" y2="46"/> <line stroke='#000000' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='78.849' y1='50.06' x2='84.195' y2='46' />
</svg> </svg>
<div className="legend-labels"> <div className='legend-labels'>
<div className="label">Rutas de bus</div> <div className='label'>Rutas de bus</div>
</div> </div>
</div> </div>
</div> </div>
@@ -77,7 +76,7 @@ export default class InfoPopUp extends React.Component{
) )
} }
render() { render () {
return ( return (
<div>{this.renderView2DLegend()}</div> <div>{this.renderView2DLegend()}</div>
) )

View File

@@ -1,3 +1,4 @@
/* global L */
import React from 'react' import React from 'react'
import { Portal } from 'react-portal' import { Portal } from 'react-portal'
@@ -7,8 +8,6 @@ import * as selectors from '../selectors'
import hash from 'object-hash' import hash from 'object-hash'
import 'leaflet' import 'leaflet'
import { isNotNullNorUndefined } from '../js/utilities'
import Sites from './presentational/Map/Sites.jsx' import Sites from './presentational/Map/Sites.jsx'
import Shapes from './presentational/Map/Shapes.jsx' import Shapes from './presentational/Map/Shapes.jsx'
import Events from './presentational/Map/Events.jsx' import Events from './presentational/Map/Events.jsx'
@@ -21,7 +20,7 @@ const supportedMapboxMap = ['streets', 'satellite']
const defaultToken = 'your_token' const defaultToken = 'your_token'
class Map extends React.Component { class Map extends React.Component {
constructor() { constructor () {
super() super()
this.projectPoint = this.projectPoint.bind(this) this.projectPoint = this.projectPoint.bind(this)
this.svgRef = React.createRef() this.svgRef = React.createRef()
@@ -33,18 +32,18 @@ class Map extends React.Component {
this.styleLocation = this.styleLocation.bind(this) this.styleLocation = this.styleLocation.bind(this)
} }
componentDidMount(){ componentDidMount () {
if (this.map === null) { if (this.map === null) {
this.initializeMap() this.initializeMap()
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps (nextProps) {
// Set appropriate zoom for narrative // Set appropriate zoom for narrative
const { bounds } = nextProps.app.map const { bounds } = nextProps.app.map
if (hash(bounds) !== hash(this.props.app.map.bounds) if (hash(bounds) !== hash(this.props.app.map.bounds) &&
&& bounds !== null) { bounds !== null) {
this.map.fitBounds(bounds) this.map.fitBounds(bounds)
} else { } else {
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
// Fly to first of events selected // Fly to first of events selected
@@ -57,7 +56,7 @@ class Map extends React.Component {
} }
} }
initializeMap() { initializeMap () {
/** /**
* Creates a Leaflet map and a tilelayer for the map background * Creates a Leaflet map and a tilelayer for the map background
*/ */
@@ -69,22 +68,22 @@ class Map extends React.Component {
.setMaxZoom(mapConf.maxZoom) .setMaxZoom(mapConf.maxZoom)
.setMaxBounds(mapConf.maxBounds) .setMaxBounds(mapConf.maxBounds)
let s let firstLayer
if ((supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1) && process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) { if ((supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1) && process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) {
s = L.tileLayer( firstLayer = L.tileLayer(
`http://a.tiles.mapbox.com/v4/mapbox.${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}` `http://a.tiles.mapbox.com/v4/mapbox.${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
) )
} else if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) { } else if (process.env.MAPBOX_TOKEN && process.env.MAPBOX_TOKEN !== defaultToken) {
s = L.tileLayer( firstLayer = L.tileLayer(
`http://a.tiles.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}` `http://a.tiles.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}`
) )
} else { } else {
s = L.tileLayer( firstLayer = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
) )
} }
s = s.addTo(map) firstLayer.addTo(map)
map.keyboard.disable() map.keyboard.disable()
@@ -96,7 +95,7 @@ class Map extends React.Component {
this.map = map this.map = map
} }
alignLayers() { alignLayers () {
const mapNode = document.querySelector('.leaflet-map-pane') const mapNode = document.querySelector('.leaflet-map-pane')
if (mapNode === null) return { transformX: 0, transformY: 0 } if (mapNode === null) return { transformX: 0, transformY: 0 }
@@ -113,7 +112,7 @@ class Map extends React.Component {
}) })
} }
projectPoint(location) { projectPoint (location) {
const latLng = new L.LatLng(location[0], location[1]) const latLng = new L.LatLng(location[0], location[1])
return { return {
x: this.map.latLngToLayerPoint(latLng).x + this.state.mapTransformX, x: this.map.latLngToLayerPoint(latLng).x + this.state.mapTransformX,
@@ -121,7 +120,7 @@ class Map extends React.Component {
} }
} }
getClientDims() { getClientDims () {
const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect() const boundingClient = document.querySelector(`#${this.props.ui.dom.map}`).getBoundingClientRect()
return { return {
@@ -130,7 +129,7 @@ class Map extends React.Component {
} }
} }
renderTiles() { renderTiles () {
const pane = this.map.getPanes().overlayPane const pane = this.map.getPanes().overlayPane
const { width, height } = this.getClientDims() const { width, height } = this.getClientDims()
@@ -140,15 +139,14 @@ class Map extends React.Component {
ref={this.svgRef} ref={this.svgRef}
width={width} width={width}
height={height} height={height}
style={{ transform: `translate3d(${-this.state.mapTransformX}px, ${-this.state.mapTransformY}px, 0)`}} style={{ transform: `translate3d(${-this.state.mapTransformX}px, ${-this.state.mapTransformY}px, 0)` }}
className='leaflet-svg' className='leaflet-svg'
> />
</svg>
</Portal> </Portal>
) )
} }
renderSites() { renderSites () {
return ( return (
<Sites <Sites
sites={this.props.domain.sites} sites={this.props.domain.sites}
@@ -158,7 +156,7 @@ class Map extends React.Component {
) )
} }
renderShapes() { renderShapes () {
return ( return (
<Shapes <Shapes
svg={this.svgRef.current} svg={this.svgRef.current}
@@ -169,7 +167,7 @@ class Map extends React.Component {
) )
} }
renderNarratives() { renderNarratives () {
return ( return (
<Narratives <Narratives
svg={this.svgRef.current} svg={this.svgRef.current}
@@ -192,7 +190,7 @@ class Map extends React.Component {
* at the second index is an optional function that renders additional * at the second index is an optional function that renders additional
* components in the <g/> div. * components in the <g/> div.
*/ */
styleLocation(location) { styleLocation (location) {
const noEvents = location.events.length const noEvents = location.events.length
return [ return [
null, null,
@@ -200,7 +198,7 @@ class Map extends React.Component {
] ]
} }
renderEvents() { renderEvents () {
return ( return (
<Events <Events
svg={this.svgRef.current} svg={this.svgRef.current}
@@ -216,7 +214,7 @@ class Map extends React.Component {
) )
} }
renderSelected() { renderSelected () {
return ( return (
<SelectedEvents <SelectedEvents
svg={this.svgRef.current} svg={this.svgRef.current}
@@ -226,8 +224,7 @@ class Map extends React.Component {
) )
} }
renderMarkers () {
renderMarkers() {
return ( return (
<Portal node={this.svgRef.current}> <Portal node={this.svgRef.current}>
<DefsMarkers /> <DefsMarkers />
@@ -235,11 +232,10 @@ class Map extends React.Component {
) )
} }
render () {
render() {
const { isShowingSites } = this.props.app.flags const { isShowingSites } = this.props.app.flags
const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper' const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper'
const innerMap = !!this.map ? ( const innerMap = this.map ? (
<React.Fragment> <React.Fragment>
{this.renderTiles()} {this.renderTiles()}
{this.renderMarkers()} {this.renderMarkers()}
@@ -260,7 +256,7 @@ class Map extends React.Component {
} }
} }
function mapStateToProps(state) { function mapStateToProps (state) {
return { return {
domain: { domain: {
locations: selectors.selectLocations(state), locations: selectors.selectLocations(state),
@@ -289,4 +285,3 @@ function mapStateToProps(state) {
} }
export default connect(mapStateToProps)(Map) export default connect(mapStateToProps)(Map)

View File

@@ -1,31 +1,31 @@
import React from 'react' /* global fetch */
import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import marked from 'marked' import marked from 'marked'
class Md extends React.Component { class Md extends React.Component {
constructor(props) { constructor (props) {
super(props) super(props)
this.state = { md: null, error: null } this.state = { md: null, error: null }
} }
componentDidMount() { componentDidMount () {
fetch(this.props.path) fetch(this.props.path)
.then(resp => resp.text()) .then(resp => resp.text())
.then(text => { .then(text => {
if (text.length <= 0) if (text.length <= 0) { throw new Error() }
throw new Error()
this.setState({ md: marked(text) }) this.setState({ md: marked(text) })
}) })
.catch(err => { .catch(() => {
this.setState({ error: true }) this.setState({ error: true })
}) })
} }
render() { render () {
if (this.state.md && !this.state.error) { if (this.state.md && !this.state.error) {
return ( return (
<div className="md-container" dangerouslySetInnerHTML={{ __html: this.state.md }} /> <div className='md-container' dangerouslySetInnerHTML={{ __html: this.state.md }} />
) )
} else if (this.state.error) { } else if (this.state.error) {
return this.props.unloader || <div>Error: couldn't load source</div> return this.props.unloader || <div>Error: couldn't load source</div>

View File

@@ -1,34 +1,33 @@
import React from 'react'; import React from 'react'
export default class Notification extends React.Component{ export default class Notification extends React.Component {
constructor (props) {
constructor(props) { super()
super();
this.state = { this.state = {
isExtended: false isExtended: false
} }
} }
toggleDetails() { toggleDetails () {
this.setState({ isExtended: !this.state.isExtended }); this.setState({ isExtended: !this.state.isExtended })
} }
renderItems(items) { renderItems (items) {
if (!items) return ''; if (!items) return ''
return ( return (
<div> <div>
{items.map((item) => { {items.map((item) => {
if (item.error) { if (item.error) {
return (<p>{item.error.message}</p>); return (<p>{item.error.message}</p>)
} }
return ''; return ''
})} })}
</div> </div>
) )
} }
renderNotificationContent(notification) { renderNotificationContent (notification) {
let { type, message, items } = notification; let { type, message, items } = notification
return ( return (
<div> <div>
@@ -42,28 +41,28 @@ export default class Notification extends React.Component{
) )
} }
render() { render () {
const notificationsToRender = this.props.notifications.filter(n => !('isRead' in n && n.isRead)) const notificationsToRender = this.props.notifications.filter(n => !('isRead' in n && n.isRead))
if (notificationsToRender.length > 0) { if (notificationsToRender.length > 0) {
return ( return (
<div className={`notification-wrapper`}> <div className={`notification-wrapper`}>
{this.props.notifications.map((notification) => { {this.props.notifications.map((notification) => {
return ( return (
<div className='notification' onClick={() => this.toggleDetails() }> <div className='notification' onClick={() => this.toggleDetails()}>
<button <button
onClick={this.props.onToggle} onClick={this.props.onToggle}
className="side-menu-burg over-white is-active" className='side-menu-burg over-white is-active'
> >
<span /> <span />
</button> </button>
{this.renderNotificationContent(notification)} {this.renderNotificationContent(notification)}
</div> </div>
); )
}) })
} }
</div> </div>
) )
} }
return (<div/>); return (<div />)
} }
} }

View File

@@ -1,40 +1,41 @@
import React from 'react'; /* global fetch */
import copy from '../js/data/copy.json'; import React from 'react'
import TagFilter from './TagFilter.jsx'; import copy from '../js/data/copy.json'
import TagFilter from './TagFilter.jsx'
export default class Search extends React.Component { export default class Search extends React.Component {
constructor(props) { constructor (props) {
super(props); super(props)
this.state = { this.state = {
searchValue: undefined, searchValue: undefined,
searchResults: [] searchResults: []
} }
this.handleSearchChange = this.handleSearchChange.bind(this); this.handleSearchChange = this.handleSearchChange.bind(this)
this.handleSearchSubmit = this.handleSearchSubmit.bind(this); this.handleSearchSubmit = this.handleSearchSubmit.bind(this)
} }
handleSearchSubmit(e) { handleSearchSubmit (e) {
e.preventDefault(); e.preventDefault()
fetch(`api/search/${this.state.searchValue}`) fetch(`api/search/${this.state.searchValue}`)
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
this.setState({ this.setState({
searchResults: json.tags searchResults: json.tags
}) })
}); })
} }
handleSearchChange(event) { handleSearchChange (event) {
this.setState({ searchValue: event.target.value }); this.setState({ searchValue: event.target.value })
} }
renderSearchResults() { renderSearchResults () {
return ( return (
this.state.searchResults.map(tag => { this.state.searchResults.map(tag => {
return ( return (
<TagFilter <TagFilter
isShowTree={true} isShowTree
tags={this.props.tags} tags={this.props.tags}
categories={this.props.categories} categories={this.props.categories}
tagFilters={this.props.tagFilters} tagFilters={this.props.tagFilters}
@@ -43,22 +44,22 @@ export default class Search extends React.Component {
tag={tag} tag={tag}
isCategory={this.props.isCategory} isCategory={this.props.isCategory}
/> />
); )
}) })
); )
} }
render() { render () {
return ( return (
<div className="search-content"> <div className='search-content'>
<h2>{copy[this.props.language].toolbar.panels.search.title}</h2> <h2>{copy[this.props.language].toolbar.panels.search.title}</h2>
<form onSubmit={this.handleSearchSubmit}> <form onSubmit={this.handleSearchSubmit}>
<input <input
value={this.state.searchValue} value={this.state.searchValue}
onChange={this.handleSearchChange} onChange={this.handleSearchChange}
autoFocus autoFocus
type="text" type='text'
name="search-input" name='search-input'
placeholder={copy[this.props.language].toolbar.panels.search.placeholder} placeholder={copy[this.props.language].toolbar.panels.search.placeholder}
/> />
</form> </form>
@@ -66,6 +67,6 @@ export default class Search extends React.Component {
{this.renderSearchResults()} {this.renderSearchResults()}
</ul> </ul>
</div> </div>
); )
}
} }
}

View File

@@ -7,8 +7,7 @@ import NoSource from './presentational/NoSource'
// TODO: move render functions into presentational components // TODO: move render functions into presentational components
class SourceOverlay extends React.Component { class SourceOverlay extends React.Component {
constructor () {
constructor() {
super() super()
this.state = { this.state = {
@@ -16,29 +15,29 @@ class SourceOverlay extends React.Component {
} }
} }
renderError() { renderError () {
return ( return (
<NoSource failedUrls={["NOT ALL SOURCES AVAILABLE IN APPLICATION YET"]} /> <NoSource failedUrls={['NOT ALL SOURCES AVAILABLE IN APPLICATION YET']} />
) )
} }
renderImage(path) { renderImage (path) {
return ( return (
<div className='source-image-container'> <div className='source-image-container'>
<Img <Img
className='source-image' className='source-image'
src={path} src={path}
loader={<div style={{ width: '400px', height: '400px' }}><Spinner /></div>} loader={<div style={{ width: '400px', height: '400px' }}><Spinner /></div>}
unloader={<NoSource failedUrls={this.props.source.paths} />} unloader={<NoSource failedUrls={this.props.source.paths} />}
/> />
</div> </div>
) )
} }
renderVideo(path) { renderVideo (path) {
// NB: assume only one video // NB: assume only one video
return ( return (
<div className="media-player"> <div className='media-player'>
<Player <Player
className='source-video' className='source-video'
playsInline playsInline
@@ -48,7 +47,7 @@ class SourceOverlay extends React.Component {
) )
} }
renderText(path) { renderText (path) {
return ( return (
<div className='source-text-container'> <div className='source-text-container'>
<Md <Md
@@ -60,15 +59,14 @@ class SourceOverlay extends React.Component {
) )
} }
renderNoSupport (ext) {
renderNoSupport(ext) {
return ( return (
<NoSource failedUrls={[`Application does not support extension: ${ext}`]} /> <NoSource failedUrls={[`Application does not support extension: ${ext}`]} />
) )
} }
toMedia(path) { toMedia (path) {
let type; let type
switch (true) { switch (true) {
case /\.(png|jpg)$/.test(path): case /\.(png|jpg)$/.test(path):
type = 'Image'; break type = 'Image'; break
@@ -82,7 +80,7 @@ class SourceOverlay extends React.Component {
return { type, path } return { type, path }
} }
getTypeCounts(media) { getTypeCounts (media) {
let counts = { Image: 0, Video: 0, Text: 0 } let counts = { Image: 0, Video: 0, Text: 0 }
media.forEach(m => { media.forEach(m => {
counts[m.type] += 1 counts[m.type] += 1
@@ -90,7 +88,7 @@ class SourceOverlay extends React.Component {
return counts return counts
} }
_renderPath(media) { _renderPath (media) {
const { path, type } = media const { path, type } = media
switch (type) { switch (type) {
case 'Image': case 'Image':
@@ -104,101 +102,99 @@ class SourceOverlay extends React.Component {
} }
} }
_renderCounts(counts) { _renderCounts (counts) {
const strFor = type => const strFor = type =>
counts[type] > 0 ? counts[type] > 0
`${counts[type]} ${type.toLowerCase()}${counts[type] > 1 ? 's': ''}` ? `${counts[type]} ${type.toLowerCase()}${counts[type] > 1 ? 's' : ''}`
: '' : ''
const img = strFor('Image') const img = strFor('Image')
const vid = strFor('Video') const vid = strFor('Video')
const txt = strFor('Text') const txt = strFor('Text')
return ( return (
<div> <div>
{img ? img : ''} {img || ''}
{(img && vid) ? `, ${vid}`: (vid || '')} {(img && vid) ? `, ${vid}` : (vid || '')}
{((img || vid) && txt) ? `, ${txt}`: (txt || '')} {((img || vid) && txt) ? `, ${txt}` : (txt || '')}
</div> </div>
) )
} }
_renderContent(media) { _renderContent (media) {
const el = document.querySelector(`.source-media-gallery`); const el = document.querySelector(`.source-media-gallery`)
const shiftW = (!!el) ? el.getBoundingClientRect().width : 0; const shiftW = (el) ? el.getBoundingClientRect().width : 0
return ( return (
<div className="source-media-gallery" style={{ transition: 'transform 0.2s ease', transform: `translate(${this.state.idx * -shiftW}px)`}}> <div className='source-media-gallery' style={{ transition: 'transform 0.2s ease', transform: `translate(${this.state.idx * -shiftW}px)` }}>
{media.map((m) => this._renderPath(m))} {media.map((m) => this._renderPath(m))}
</div> </div>
) )
} }
onShiftGallery(shift) { onShiftGallery (shift) {
if (this.state.idx === 0 && shift === -1) return; if (this.state.idx === 0 && shift === -1) return
if (this.state.idx - 1 === this.props.source.paths.length && shift === 1) return if (this.state.idx - 1 === this.props.source.paths.length && shift === 1) return
this.setState({ idx: this.state.idx+shift }); this.setState({ idx: this.state.idx + shift })
} }
_renderControls() { _renderControls () {
if (this.props.source.paths.length > 1) { if (this.props.source.paths.length > 1) {
return ( return (
<div className="media-gallery-controls"> <div className='media-gallery-controls'>
<div className="back" onClick={() => this.onShiftGallery(-1)}><svg><path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z"></path></svg></div> <div className='back' onClick={() => this.onShiftGallery(-1)}><svg><path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' /></svg></div>
<div className="next" onClick={() => this.onShiftGallery(1)}><svg><path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z"></path></svg></div> <div className='next' onClick={() => this.onShiftGallery(1)}><svg><path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' /></svg></div>
</div> </div>
); )
} }
return ( return (
<div className="media-gallery-controls"></div> <div className='media-gallery-controls' />
); )
} }
render () { render () {
if (typeof(this.props.source) !== 'object') { if (typeof (this.props.source) !== 'object') {
return this.renderError() return this.renderError()
} }
const {id, url, title, paths, date, type, desc} = this.props.source const { url, title, paths, date, type, desc } = this.props.source
const media = paths.map(this.toMedia) const media = paths.map(this.toMedia)
const counts = this.getTypeCounts(media)
return ( return (
<div className="mo-overlay" onClick={this.props.onCancel}> <div className='mo-overlay' onClick={this.props.onCancel}>
<div className="mo-container" onClick={(e) => { e.stopPropagation(); }}> <div className='mo-container' onClick={(e) => { e.stopPropagation() }}>
<div className="mo-header"> <div className='mo-header'>
<div className="mo-header-close" onClick={this.props.onCancel}> <div className='mo-header-close' onClick={this.props.onCancel}>
<button className="side-menu-burg is-active"><span></span></button> <button className='side-menu-burg is-active'><span /></button>
</div> </div>
<div className="mo-header-text">{this.props.source.title}</div> <div className='mo-header-text'>{this.props.source.title}</div>
</div> </div>
<div className="mo-media-container"> <div className='mo-media-container'>
{this._renderContent(media)} {this._renderContent(media)}
{this._renderControls()} {this._renderControls()}
</div> </div>
<div className="mo-meta-container"> <div className='mo-meta-container'>
<div className="mo-box-title"> <div className='mo-box-title'>
{/* <p>{`${this.state.idx+1} / ${paths.length}`}</p> */} {/* <p>{`${this.state.idx+1} / ${paths.length}`}</p> */}
{title? <p><b>{title}</b></p> : null} {title ? <p><b>{title}</b></p> : null}
<div>{desc}</div> <div>{desc}</div>
</div> </div>
<div className="mo-box"> <div className='mo-box'>
<div> <div>
{type ? <h4>Media type</h4> : null} {type ? <h4>Media type</h4> : null}
{type ? <p><i className="material-icons left">perm_media</i>{type}</p> : null} {type ? <p><i className='material-icons left'>perm_media</i>{type}</p> : null}
</div> </div>
<div> <div>
{date ? <h4>Date</h4> : null} {date ? <h4>Date</h4> : null}
{date ? <p><i className="material-icons left">today</i>{date}</p>: null} {date ? <p><i className='material-icons left'>today</i>{date}</p> : null}
</div> </div>
<div> <div>
{url ? <h4>Link</h4> : null} {url ? <h4>Link</h4> : null}
{url ? <span><i className="material-icons left">link</i><a href={url} target="_blank">Link to original URL</a></span> : null} {url ? <span><i className='material-icons left'>link</i><a href={url} target='_blank'>Link to original URL</a></span> : null}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
} }

View File

@@ -1,48 +1,44 @@
import React from 'react'; import React from 'react'
import Checkbox from './presentational/Checkbox'; import Checkbox from './presentational/Checkbox'
class TagFilter extends React.Component { class TagFilter extends React.Component {
constructor(props) { isActive () {
super(props);
}
isActive() {
if (this.props.isCategory) { if (this.props.isCategory) {
return this.props.categoryFilters.includes(this.props.tag.id); return this.props.categoryFilters.includes(this.props.tag.id)
} }
return this.props.tagFilters.includes(this.props.tag.id); return this.props.tagFilters.includes(this.props.tag.id)
} }
onClickTag() { onClickTag () {
if (this.isActive()) { if (this.isActive()) {
this.props.filter({ this.props.filter({
tags: this.props.tagFilters.filter(element => element !== this.props.tag.id) tags: this.props.tagFilters.filter(element => element !== this.props.tag.id)
}); })
} else { } else {
this.props.filter({ this.props.filter({
tags: this.props.tagFilters.concat(this.props.tag.id) tags: this.props.tagFilters.concat(this.props.tag.id)
}); })
} }
} }
onClickCategory() { onClickCategory () {
if (this.isActive()) { if (this.isActive()) {
this.props.filter({ this.props.filter({
categories: this.props.categoryFilters.filter(element => element !== this.props.tag.id) categories: this.props.categoryFilters.filter(element => element !== this.props.tag.id)
}); })
} else { } else {
this.props.filter({ this.props.filter({
categories: this.props.categoryFilters.concat(this.props.tag.id) categories: this.props.categoryFilters.concat(this.props.tag.id)
}); })
} }
} }
renderTag() { renderTag () {
const tag = this.props.tag; const tag = this.props.tag
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'; let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
let label = `${tag.name} ( ${tag.mentions} )`; let label = `${tag.name} ( ${tag.mentions} )`
if (this.props.isShowTree) { if (this.props.isShowTree) {
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`; label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`
} }
return ( return (
<li <li
@@ -55,12 +51,12 @@ class TagFilter extends React.Component {
onClickCheckbox={() => this.onClickTag()} onClickCheckbox={() => this.onClickTag()}
/> />
</li> </li>
); )
} }
renderCategory() { renderCategory () {
const category = this.props.categories[this.props.tag.id]; const category = this.props.categories[this.props.tag.id]
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'; let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
if (category) { if (category) {
return ( return (
@@ -74,15 +70,15 @@ class TagFilter extends React.Component {
onClickCheckbox={() => this.onClickCategory()} onClickCheckbox={() => this.onClickCategory()}
/> />
</li> </li>
); )
} }
return (<div/>); return (<div />)
} }
render() { render () {
if (this.props.isCategory) return (this.renderCategory()); if (this.props.isCategory) return (this.renderCategory())
return (this.renderTag()); return (this.renderTag())
} }
} }
export default TagFilter; export default TagFilter

View File

@@ -1,68 +1,67 @@
import React from 'react'; import React from 'react'
import Checkbox from './presentational/Checkbox'; import Checkbox from './presentational/Checkbox'
import copy from '../js/data/copy.json'; import copy from '../js/data/copy.json'
class TagListPanel extends React.Component { class TagListPanel extends React.Component {
constructor (props) {
constructor(props) { super(props)
super(props);
this.state = { this.state = {
treeComponents: [] treeComponents: []
} }
this.treeComponents = []; this.treeComponents = []
this.newTagFilters = []; this.newTagFilters = []
} }
componentDidMount() { componentDidMount () {
this.computeTree(this.props.tags);//.children[this.props.tagType]); this.computeTree(this.props.tags)// .children[this.props.tagType]);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps (nextProps) {
this.computeTree(nextProps.tags);//.children[nextProps.tagType]); this.computeTree(nextProps.tags)// .children[nextProps.tagType]);
} }
onClickCheckbox(obj, type) { onClickCheckbox (obj, type) {
obj.active = !obj.active obj.active = !obj.active
if (type === 'category') this.props.onCategoryFilter(obj); if (type === 'category') this.props.onCategoryFilter(obj)
if (type === 'tag') this.props.onTagFilter(obj); if (type === 'tag') this.props.onTagFilter(obj)
} }
createNodeComponent (node, depth) { createNodeComponent (node, depth) {
return ( return (
<li <li
key={node.key.replace(/ /g,"_")} key={node.key.replace(/ /g, '_')}
className={'tag-filter active'} className={'tag-filter active'}
style={{ marginLeft: `${depth*20}px` }} style={{ marginLeft: `${depth * 20}px` }}
> >
<Checkbox <Checkbox
label={node.key} label={node.key}
isActive={node.active} isActive={node.active}
onClickCheckbox={() => this.onClickCheckbox(node, 'tag')} onClickCheckbox={() => this.onClickCheckbox(node, 'tag')}
/> />
</li> </li>
); )
} }
traverseNodeAndCreateComponent(node, depth) { traverseNodeAndCreateComponent (node, depth) {
// add and create node component // add and create node component
const newComponent = this.createNodeComponent(node, depth); const newComponent = this.createNodeComponent(node, depth)
this.treeComponents.push(newComponent) this.treeComponents.push(newComponent)
depth = depth + 1; depth = depth + 1
if (Object.keys(node.children).length > 0) { if (Object.keys(node.children).length > 0) {
Object.values(node.children).forEach((childNode) => { Object.values(node.children).forEach((childNode) => {
this.traverseNodeAndCreateComponent(childNode, depth); this.traverseNodeAndCreateComponent(childNode, depth)
}); })
} }
} }
computeTree (node) { computeTree (node) {
this.treeComponents = []; this.treeComponents = []
let depth = 0; let depth = 0
this.traverseNodeAndCreateComponent(node, depth); this.traverseNodeAndCreateComponent(node, depth)
this.setState({ treeComponents: this.treeComponents }); this.setState({ treeComponents: this.treeComponents })
} }
renderTree() { renderTree () {
return ( return (
<div> <div>
<h2>{copy[this.props.language].toolbar.tags}</h2> <h2>{copy[this.props.language].toolbar.tags}</h2>
@@ -71,13 +70,13 @@ class TagListPanel extends React.Component {
) )
} }
renderCategoryTree() { renderCategoryTree () {
return ( return (
<div> <div>
<h2>{copy[this.props.language].toolbar.categories}</h2> <h2>{copy[this.props.language].toolbar.categories}</h2>
{this.props.categories.map(cat => { {this.props.categories.map(cat => {
return (<li return (<li
key={cat.category.replace(/ /g,"_")} key={cat.category.replace(/ /g, '_')}
className={'tag-filter active'} className={'tag-filter active'}
style={{ marginLeft: '20px' }} style={{ marginLeft: '20px' }}
> >
@@ -87,22 +86,22 @@ class TagListPanel extends React.Component {
onClickCheckbox={() => this.onClickCheckbox(cat, 'category')} onClickCheckbox={() => this.onClickCheckbox(cat, 'category')}
/> />
</li>) </li>)
}) })
} }
</div> </div>
) )
} }
render() { render () {
return ( return (
<div className="react-innertabpanel"> <div className='react-innertabpanel'>
<h2>{copy[this.props.language].toolbar.explore_by_tag__title}</h2> <h2>{copy[this.props.language].toolbar.explore_by_tag__title}</h2>
<p>{copy[this.props.language].toolbar.explore_by_tag__description}</p> <p>{copy[this.props.language].toolbar.explore_by_tag__description}</p>
{this.renderCategoryTree()} {this.renderCategoryTree()}
{this.renderTree()} {this.renderTree()}
</div> </div>
); )
} }
} }
export default TagListPanel; export default TagListPanel

View File

@@ -1,3 +1,4 @@
/* global d3 */
import React from 'react' import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import * as selectors from '../selectors' import * as selectors from '../selectors'
@@ -10,13 +11,12 @@ import Axis from './TimelineAxis.jsx'
import Clip from './presentational/Timeline/Clip' import Clip from './presentational/Timeline/Clip'
import Handles from './presentational/Timeline/Handles.js' import Handles from './presentational/Timeline/Handles.js'
import ZoomControls from './presentational/Timeline/ZoomControls.js' import ZoomControls from './presentational/Timeline/ZoomControls.js'
import Labels from './presentational/Timeline/Labels.js'
import Markers from './presentational/Timeline/Markers.js' import Markers from './presentational/Timeline/Markers.js'
import Events from './presentational/Timeline/Events.js' import Events from './presentational/Timeline/Events.js'
import Categories from './TimelineCategories.jsx' import Categories from './TimelineCategories.jsx'
class Timeline extends React.Component { class Timeline extends React.Component {
constructor(props) { constructor (props) {
super(props) super(props)
this.styleDatetime = this.styleDatetime.bind(this) this.styleDatetime = this.styleDatetime.bind(this)
this.getDatetimeX = this.getDatetimeX.bind(this) this.getDatetimeX = this.getDatetimeX.bind(this)
@@ -33,12 +33,12 @@ class Timeline extends React.Component {
} }
} }
componentDidMount() { componentDidMount () {
this.computeDims() this.computeDims()
this.addEventListeners() this.addEventListeners()
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps (nextProps) {
if (hash(nextProps) !== hash(this.props)) { if (hash(nextProps) !== hash(this.props)) {
this.setState({ this.setState({
timerange: nextProps.app.timeline.range, timerange: nextProps.app.timeline.range,
@@ -59,22 +59,21 @@ class Timeline extends React.Component {
} }
} }
addEventListeners() { addEventListeners () {
window.addEventListener('resize', () => { this.computeDims() }) window.addEventListener('resize', () => { this.computeDims() })
let element = document.querySelector('.timeline-wrapper') let element = document.querySelector('.timeline-wrapper')
element.addEventListener("transitionend", (event) => { element.addEventListener('transitionend', (event) => {
this.computeDims() this.computeDims()
}) })
} }
makeScaleX() { makeScaleX () {
return d3.scaleTime() return d3.scaleTime()
.domain(this.state.timerange) .domain(this.state.timerange)
.range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls]) .range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls])
} }
makeScaleY(categories) { makeScaleY (categories) {
const tickHeight = 15 const tickHeight = 15
const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2) const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2)
return d3.scaleOrdinal() return d3.scaleOrdinal()
@@ -82,7 +81,7 @@ class Timeline extends React.Component {
.range(catsYpos) .range(catsYpos)
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
if (prevState.timerange !== this.state.timerange) { if (prevState.timerange !== this.state.timerange) {
this.setState({ scaleX: this.makeScaleX() }) this.setState({ scaleX: this.makeScaleX() })
} }
@@ -91,19 +90,19 @@ class Timeline extends React.Component {
/** /**
* Returns the time scale (x) extent in minutes * Returns the time scale (x) extent in minutes
*/ */
getTimeScaleExtent() { getTimeScaleExtent () {
if (!this.state.scaleX) return 0 if (!this.state.scaleX) return 0
const timeDomain = this.state.scaleX.domain() const timeDomain = this.state.scaleX.domain()
return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000 return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000
} }
onClickArrow() { onClickArrow () {
this.setState((prevState, props) => { this.setState((prevState, props) => {
return {isFolded: !prevState.isFolded} return { isFolded: !prevState.isFolded }
}) })
} }
computeDims() { computeDims () {
const dom = this.props.ui.dom.timeline const dom = this.props.ui.dom.timeline
if (document.querySelector(`#${dom}`) !== null) { if (document.querySelector(`#${dom}`) !== null) {
const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect() const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect()
@@ -114,18 +113,18 @@ class Timeline extends React.Component {
width: boundingClient.width width: boundingClient.width
} }
}, },
() => { () => {
this.setState({ scaleX: this.makeScaleX() this.setState({ scaleX: this.makeScaleX()
})
}) })
})
} }
} }
/** /**
* Shift time range by moving forward or backwards * Shift time range by moving forward or backwards
* @param {String} direction: 'forward' / 'backwards' * @param {String} direction: 'forward' / 'backwards'
*/ */
onMoveTime(direction) { onMoveTime (direction) {
this.props.methods.onSelect() this.props.methods.onSelect()
const extent = this.getTimeScaleExtent() const extent = this.getTimeScaleExtent()
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2) const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
@@ -145,11 +144,11 @@ class Timeline extends React.Component {
}) })
} }
onCenterTime(newCentralTime) { onCenterTime (newCentralTime) {
const extent = this.getTimeScaleExtent() const extent = this.getTimeScaleExtent()
const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2) const domain0 = d3.timeMinute.offset(newCentralTime, -extent / 2)
const domainF = d3.timeMinute.offset(newCentralTime, +extent/2) const domainF = d3.timeMinute.offset(newCentralTime, +extent / 2)
this.setState({ timerange: [domain0, domainF] }, () => { this.setState({ timerange: [domain0, domainF] }, () => {
this.props.methods.onUpdateTimerange(this.state.timerange) this.props.methods.onUpdateTimerange(this.state.timerange)
@@ -161,7 +160,7 @@ class Timeline extends React.Component {
* WITHOUT updating the store, or data shown. * WITHOUT updating the store, or data shown.
* Used for updates in the middle of a transition, for performance purposes * Used for updates in the middle of a transition, for performance purposes
*/ */
onSoftTimeRangeUpdate(timerange) { onSoftTimeRangeUpdate (timerange) {
this.setState({ timerange }) this.setState({ timerange })
} }
@@ -169,26 +168,26 @@ class Timeline extends React.Component {
* Apply zoom level to timeline * Apply zoom level to timeline
* @param {object} zoom: zoom level from zoomLevels * @param {object} zoom: zoom level from zoomLevels
*/ */
onApplyZoom(zoom) { onApplyZoom (zoom) {
const extent = this.getTimeScaleExtent() const extent = this.getTimeScaleExtent()
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2) const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
this.setState({ timerange: [ this.setState({ timerange: [
d3.timeMinute.offset(newCentralTime, -zoom.duration / 2), d3.timeMinute.offset(newCentralTime, -zoom.duration / 2),
d3.timeMinute.offset(newCentralTime, zoom.duration / 2) d3.timeMinute.offset(newCentralTime, zoom.duration / 2)
]}, () => { ] }, () => {
this.props.methods.onUpdateTimerange(this.state.timerange) this.props.methods.onUpdateTimerange(this.state.timerange)
}) })
} }
toggleTransition(isTransition) { toggleTransition (isTransition) {
this.setState({ transitionDuration: (isTransition) ? 300 : 0 }) this.setState({ transitionDuration: (isTransition) ? 300 : 0 })
} }
/* /*
* Setup drag behavior * Setup drag behavior
*/ */
onDragStart() { onDragStart () {
d3.event.sourceEvent.stopPropagation() d3.event.sourceEvent.stopPropagation()
this.setState({ this.setState({
dragPos0: d3.event.x dragPos0: d3.event.x
@@ -200,7 +199,7 @@ class Timeline extends React.Component {
/* /*
* Drag and update * Drag and update
*/ */
onDrag() { onDrag () {
const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime() const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime()
const dragNow = this.state.scaleX.invert(d3.event.x).getTime() const dragNow = this.state.scaleX.invert(d3.event.x).getTime()
const timeShift = (drag0 - dragNow) / 1000 const timeShift = (drag0 - dragNow) / 1000
@@ -216,12 +215,12 @@ class Timeline extends React.Component {
/** /**
* Stop dragging and update data * Stop dragging and update data
*/ */
onDragEnd() { onDragEnd () {
this.toggleTransition(true) this.toggleTransition(true)
this.props.methods.onUpdateTimerange(this.state.timerange) this.props.methods.onUpdateTimerange(this.state.timerange)
} }
getDatetimeX(dt) { getDatetimeX (dt) {
return this.state.scaleX(parseDate(dt.timestamp)) return this.state.scaleX(parseDate(dt.timestamp))
} }
@@ -238,12 +237,12 @@ class Timeline extends React.Component {
* at the second index is an optional function that renders additional * at the second index is an optional function that renders additional
* components in the <g/> div. * components in the <g/> div.
*/ */
styleDatetime(timestamp, category) { styleDatetime (timestamp, category) {
return [] return []
} }
render() { render () {
const { isNarrative, app, ui } = this.props const { isNarrative, app } = this.props
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}` let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`
classes += (app.narrative !== null) ? ' narrative-mode' : '' classes += (app.narrative !== null) ? ' narrative-mode' : ''
const { dims } = this.state const { dims } = this.state
@@ -257,8 +256,8 @@ class Timeline extends React.Component {
onClick={() => { this.onClickArrow() }} onClick={() => { this.onClickArrow() }}
hideInfo={isNarrative} hideInfo={isNarrative}
/> />
<div className="timeline-content"> <div className='timeline-content'>
<div id={this.props.ui.dom.timeline} className="timeline"> <div id={this.props.ui.dom.timeline} className='timeline'>
<svg <svg
ref={this.svgRef} ref={this.svgRef}
width={dims.width} width={dims.width}
@@ -319,7 +318,7 @@ class Timeline extends React.Component {
} }
} }
function mapStateToProps(state) { function mapStateToProps (state) {
return { return {
isNarrative: !!state.app.narrative, isNarrative: !!state.app.narrative,
domain: { domain: {
@@ -334,7 +333,7 @@ function mapStateToProps(state) {
narrative: state.app.narrative narrative: state.app.narrative
}, },
ui: { ui: {
dom: state.ui.dom, dom: state.ui.dom
} }
} }
} }

View File

@@ -1,46 +1,45 @@
import React from 'react'; /* global d3 */
import React from 'react'
class TimelineAxis extends React.Component { class TimelineAxis extends React.Component {
constructor () {
constructor() { super()
super(); this.xAxis0Ref = React.createRef()
this.xAxis0Ref = React.createRef(); this.xAxis1Ref = React.createRef()
this.xAxis1Ref = React.createRef();
this.state = { this.state = {
isInitialized: false, isInitialized: false
} }
} }
componentDidUpdate () {
componentDidUpdate() {
if (this.props.scaleX) { if (this.props.scaleX) {
this.x0 = this.x0 =
d3.axisBottom(this.props.scaleX) d3.axisBottom(this.props.scaleX)
.ticks(10) .ticks(10)
.tickPadding(5) .tickPadding(5)
.tickSize(this.props.dims.trackHeight) .tickSize(this.props.dims.trackHeight)
.tickFormat(d3.timeFormat('%d %b')); .tickFormat(d3.timeFormat('%d %b'))
this.x1 = this.x1 =
d3.axisBottom(this.props.scaleX) d3.axisBottom(this.props.scaleX)
.ticks(10) .ticks(10)
.tickPadding(this.props.dims.margin_top) .tickPadding(this.props.dims.margin_top)
.tickSize(0) .tickSize(0)
.tickFormat(d3.timeFormat('%H:%M')); .tickFormat(d3.timeFormat('%H:%M'))
if (!this.state.isInitialized) this.setState({ isInitialized: true }); if (!this.state.isInitialized) this.setState({ isInitialized: true })
} }
if (this.state.isInitialized) { if (this.state.isInitialized) {
d3.select(this.xAxis0Ref.current) d3.select(this.xAxis0Ref.current)
.transition() .transition()
.duration(this.props.transitionDuration) .duration(this.props.transitionDuration)
.call(this.x0); .call(this.x0)
d3.select(this.xAxis1Ref.current) d3.select(this.xAxis1Ref.current)
.transition() .transition()
.duration(this.props.transitionDuration) .duration(this.props.transitionDuration)
.call(this.x1); .call(this.x1)
} }
} }
@@ -52,18 +51,16 @@ class TimelineAxis extends React.Component {
transform={`translate(0, 25)`} transform={`translate(0, 25)`}
clipPath={`url(#clip)`} clipPath={`url(#clip)`}
className={`axis xAxis`} className={`axis xAxis`}
> />
</g>
<g <g
ref={this.xAxis1Ref} ref={this.xAxis1Ref}
transform={`translate(0, 105)`} transform={`translate(0, 105)`}
clipPath={`url(#clip)`} clipPath={`url(#clip)`}
className={`axis axisHourText`} className={`axis axisHourText`}
> />
</g>
</React.Fragment> </React.Fragment>
); )
} }
} }
export default TimelineAxis; export default TimelineAxis

View File

@@ -1,58 +1,58 @@
import React from 'react'; /* global d3 */
import React from 'react'
class TimelineCategories extends React.Component { class TimelineCategories extends React.Component {
constructor () {
constructor() { super()
super();
this.grabRef = React.createRef() this.grabRef = React.createRef()
this.state = { this.state = {
isInitialized: false isInitialized: false
} }
} }
componentDidUpdate() { componentDidUpdate () {
if (!this.state.isInitialized) { if (!this.state.isInitialized) {
const drag = d3.drag() const drag = d3.drag()
.on('start', this.props.onDragStart) .on('start', this.props.onDragStart)
.on('drag', this.props.onDrag) .on('drag', this.props.onDrag)
.on('end', this.props.onDragEnd); .on('end', this.props.onDragEnd)
d3.select(this.grabRef.current) d3.select(this.grabRef.current)
.call(drag); .call(drag)
this.setState({ isInitialized: true }); this.setState({ isInitialized: true })
} }
} }
renderCategory(category, idx) { renderCategory (category, idx) {
const dims = this.props.dims; const dims = this.props.dims
return ( return (
<g class="tick" opacity="1" transform={`translate(0,${this.props.getCategoryY(category.category)})`}> <g class='tick' opacity='1' transform={`translate(0,${this.props.getCategoryY(category.category)})`}>
<line x1={dims.margin_left} x2={dims.width - dims.width_controls}></line> <line x1={dims.margin_left} x2={dims.width - dims.width_controls} />
<text x={dims.margin_left - 5} dy="0.32em">{category.category}</text> <text x={dims.margin_left - 5} dy='0.32em'>{category.category}</text>
</g> </g>
) )
} }
render () { render () {
const dims = this.props.dims; const dims = this.props.dims
return ( return (
<g <g
class="yAxis" class='yAxis'
> >
{this.props.categories.map((cat, idx) => this.renderCategory(cat, idx))} {this.props.categories.map((cat, idx) => this.renderCategory(cat, idx))}
<rect <rect
ref={this.grabRef} ref={this.grabRef}
class="drag-grabber" class='drag-grabber'
x={dims.margin_left} x={dims.margin_left}
y="20" y='20'
width={dims.width - dims.margin_left - dims.width_controls} width={dims.width - dims.margin_left - dims.width_controls}
height={dims.trackHeight} height={dims.trackHeight}
></rect> />
</g> </g>
); )
} }
} }
export default TimelineCategories; export default TimelineCategories

View File

@@ -4,7 +4,7 @@ import { bindActionCreators } from 'redux'
import * as actions from '../actions' import * as actions from '../actions'
import * as selectors from '../selectors' import * as selectors from '../selectors'
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import { Tabs, TabPanel } from 'react-tabs'
import Search from './Search.jsx' import Search from './Search.jsx'
import TagListPanel from './TagListPanel.jsx' import TagListPanel from './TagListPanel.jsx'
import ToolbarBottomActions from './ToolbarBottomActions.jsx' import ToolbarBottomActions from './ToolbarBottomActions.jsx'
@@ -12,25 +12,25 @@ import copy from '../js/data/copy.json'
import { trimAndEllipse } from '../js/utilities.js' import { trimAndEllipse } from '../js/utilities.js'
class Toolbar extends React.Component { class Toolbar extends React.Component {
constructor(props) { constructor (props) {
super(props) super(props)
this.state = { _selected: -1 } this.state = { _selected: -1 }
} }
selectTab(selected) { selectTab (selected) {
const _selected = (this.state._selected === selected) ? -1 : selected const _selected = (this.state._selected === selected) ? -1 : selected
this.setState({ _selected }) this.setState({ _selected })
} }
renderClosePanel() { renderClosePanel () {
return ( return (
<div className="panel-header" onClick={() => this.selectTab(-1)}> <div className='panel-header' onClick={() => this.selectTab(-1)}>
<div className="caret"></div> <div className='caret' />
</div> </div>
) )
} }
renderSearch() { renderSearch () {
if (process.env.features.USE_SEARCH) { if (process.env.features.USE_SEARCH) {
return ( return (
<TabPanel> <TabPanel>
@@ -47,19 +47,19 @@ class Toolbar extends React.Component {
} }
} }
goToNarrative(narrative) { goToNarrative (narrative) {
this.selectTab(-1) // set all unselected within this component this.selectTab(-1) // set all unselected within this component
this.props.methods.onSelectNarrative(narrative) this.props.methods.onSelectNarrative(narrative)
} }
renderToolbarNarrativePanel() { renderToolbarNarrativePanel () {
return ( return (
<TabPanel> <TabPanel>
<h2>{copy[this.props.language].toolbar.narrative_panel_title}</h2> <h2>{copy[this.props.language].toolbar.narrative_panel_title}</h2>
<p>{copy[this.props.language].toolbar.narrative_summary}</p> <p>{copy[this.props.language].toolbar.narrative_summary}</p>
{this.props.narratives.map((narr) => { {this.props.narratives.map((narr) => {
return ( return (
<div className="panel-action action"> <div className='panel-action action'>
<button style={{ backgroundColor: '#000' }} onClick={() => { this.goToNarrative(narr) }}> <button style={{ backgroundColor: '#000' }} onClick={() => { this.goToNarrative(narr) }}>
<p>{narr.label}</p> <p>{narr.label}</p>
<p><small>{trimAndEllipse(narr.description, 120)}</small></p> <p><small>{trimAndEllipse(narr.description, 120)}</small></p>
@@ -71,7 +71,7 @@ class Toolbar extends React.Component {
) )
} }
renderToolbarTagPanel() { renderToolbarTagPanel () {
if (process.env.features.USE_TAGS && if (process.env.features.USE_TAGS &&
this.props.tags.children) { this.props.tags.children) {
return ( return (
@@ -91,19 +91,20 @@ class Toolbar extends React.Component {
return '' return ''
} }
renderToolbarTab(_selected, label, icon_key) { renderToolbarTab (_selected, label, iconKey) {
console.log(label)
const isActive = (this.state._selected === _selected) const isActive = (this.state._selected === _selected)
let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab' let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'
return ( return (
<div className={classes} onClick={() => { this.selectTab(_selected) }}> <div className={classes} onClick={() => { this.selectTab(_selected) }}>
<i className="material-icons">{icon_key}</i> <i className='material-icons'>{iconKey}</i>
<div className="tab-caption">{label}</div> <div className='tab-caption'>{label}</div>
</div> </div>
) )
} }
renderToolbarPanels() { renderToolbarPanels () {
let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded' let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded'
return ( return (
<div className={classes}> <div className={classes}>
@@ -116,7 +117,7 @@ class Toolbar extends React.Component {
) )
} }
renderToolbarNavs() { renderToolbarNavs () {
if (this.props.narratives) { if (this.props.narratives) {
return this.props.narratives.map((nar, idx) => { return this.props.narratives.map((nar, idx) => {
const isActive = (idx === this.state._selected) const isActive = (idx === this.state._selected)
@@ -125,7 +126,7 @@ class Toolbar extends React.Component {
return ( return (
<div className={classes} onClick={() => { this.selectTab(idx) }}> <div className={classes} onClick={() => { this.selectTab(idx) }}>
<div className="tab-caption">{nar.label}</div> <div className='tab-caption'>{nar.label}</div>
</div> </div>
) )
}) })
@@ -133,36 +134,35 @@ class Toolbar extends React.Component {
return '' return ''
} }
renderToolbarTabs() { renderToolbarTabs () {
let title = copy[this.props.language].toolbar.title let title = copy[this.props.language].toolbar.title
if (process.env.title) title = process.env.title if (process.env.title) title = process.env.title
const narratives_label = copy[this.props.language].toolbar.narratives_label const narrativesLabel = copy[this.props.language].toolbar.narratives_label
const tags_label = copy[this.props.language].toolbar.tags_label const tagsLabel = copy[this.props.language].toolbar.tags_label
const isTags = this.props.tags && this.props.tags.children const isTags = this.props.tags && this.props.tags.children
return ( return (
<div className="toolbar"> <div className='toolbar'>
<div className="toolbar-header"><p>{title}</p></div> <div className='toolbar-header'><p>{title}</p></div>
<div className="toolbar-tabs"> <div className='toolbar-tabs'>
{/*this.renderToolbarTab(0, 'search')*/} {this.renderToolbarTab(0, narrativesLabel, 'timeline')}
{this.renderToolbarTab(0, narratives_label, 'timeline')} {(isTags) ? this.renderToolbarTab(1, tagsLabel, 'style') : ''}
{(isTags) ? this.renderToolbarTab(1, tags_label, 'style') : ''}
</div> </div>
<ToolbarBottomActions <ToolbarBottomActions
sites={{ sites={{
enabled: this.props.sitesShowing, enabled: this.props.sitesShowing,
toggle: this.props.actions.toggleSites, toggle: this.props.actions.toggleSites
}} }}
/> />
</div> </div>
) )
} }
render() { render () {
const { isNarrative } = this.props const { isNarrative } = this.props
return ( return (
<div id="toolbar-wrapper" className={`toolbar-wrapper ${(isNarrative) ? 'narrative-mode' : ''}`}> <div id='toolbar-wrapper' className={`toolbar-wrapper ${(isNarrative) ? 'narrative-mode' : ''}`}>
{this.renderToolbarTabs()} {this.renderToolbarTabs()}
{this.renderToolbarPanels()} {this.renderToolbarPanels()}
</div> </div>
@@ -170,7 +170,7 @@ class Toolbar extends React.Component {
} }
} }
function mapStateToProps(state) { function mapStateToProps (state) {
return { return {
tags: selectors.getTagTree(state), tags: selectors.getTagTree(state),
categories: selectors.getCategories(state), categories: selectors.getCategories(state),
@@ -185,7 +185,7 @@ function mapStateToProps(state) {
} }
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps (dispatch) {
return { return {
actions: bindActionCreators(actions, dispatch) actions: bindActionCreators(actions, dispatch)
} }

View File

@@ -1,33 +1,33 @@
import React from 'react'; import React from 'react'
import SitesIcon from './presentational/Icons/SitesIcon.js'; import SitesIcon from './presentational/Icons/Sites.js'
// import RefreshIcon from './presentational/Icons/RefreshIcon.js'; // import RefreshIcon from './presentational/Icons/RefreshIcon.js'
// import CoeventIcon from './presentational/Icons/CoeventIcon.js'; // import CoeventIcon from './presentational/Icons/CoeventIcon.js'
// import RouteIcon from './presentational/Icons/RouteIcon.js'; // import RouteIcon from './presentational/Icons/RouteIcon.js'
function ToolbarBottomActions (props) { function ToolbarBottomActions (props) {
function renderMapActions() { function renderMapActions () {
return ( return (
<div className="bottom-action-block"> <div className='bottom-action-block'>
{process.env.features.USE_SITES ? <SitesIcon {process.env.features.USE_SITES ? <SitesIcon
isActive={props.sites.enabled} isActive={props.sites.enabled}
onClickHandler={props.sites.toggle} onClickHandler={props.sites.toggle}
/> : null} /> : null}
</div> </div>
); )
} }
return ( return (
<div className="bottom-actions"> <div className='bottom-actions'>
{renderMapActions()} {renderMapActions()}
<div className="bottom-action-block"> <div className='bottom-action-block'>
{/* <button className="action-button tiny default" onClick={() => { toggleLanguage()}}> */} {/* <button className='action-button tiny default' onClick={() => { toggleLanguage()}}> */}
{/* {(props.language === 'es-MX') ? 'ES' : 'EN' } */} {/* {(props.language === 'es-MX') ? 'ES' : 'EN' } */}
{/* </button> */} {/* </button> */}
{/* <button className="action-button info tiny default" onClick={() => { this.toggleInfoPopup()}}> */} {/* <button className='action-button info tiny default' onClick={() => { this.toggleInfoPopup()}}> */}
{/* i */} {/* i */}
{/* </button> */} {/* </button> */}
{/* <button className="action-button tiny" onClick={() => this.resetAllFilters()}> */} {/* <button className='action-button tiny' onClick={() => this.resetAllFilters()}> */}
{/* <RefreshIcon /> */} {/* <RefreshIcon /> */}
{/* </button> */} {/* </button> */}
</div> </div>
@@ -35,4 +35,4 @@ function ToolbarBottomActions (props) {
) )
} }
export default ToolbarBottomActions; export default ToolbarBottomActions

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
const RefreshIcon = () => { export default ({ isActive, isDisabled, onClickHandler }) => {
return ( return (
<svg className='reset' x='0px' y='0px' width='25px' height='25px' viewBox='7.5 7.5 25 25' enableBackground='new 7.5 7.5 25 25'> <svg className='reset' x='0px' y='0px' width='25px' height='25px' viewBox='7.5 7.5 25 25' enableBackground='new 7.5 7.5 25 25'>
<path stroke-width='2' stroke-miterlimit='10' d='M28.822,16.386c1.354,3.219,0.898,7.064-1.5,9.924 <path stroke-width='2' stroke-miterlimit='10' d='M28.822,16.386c1.354,3.219,0.898,7.064-1.5,9.924
@@ -9,5 +9,3 @@ const RefreshIcon = () => {
</svg> </svg>
) )
} }
export default RefreshIcon

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from 'react'
const MapDefsMarkers = ({}) => ( const MapDefsMarkers = () => (
<defs> <defs>
<marker id="arrow" viewBox="0 0 6 6" refX="3" refY="3" markerWidth="6" markerHeight="6" orient="auto"> <marker id='arrow' viewBox='0 0 6 6' refX='3' refY='3' markerWidth='6' markerHeight='6' orient='auto'>
<path d="M0,3v-3l6,3l-6,3z" style={{ fill: 'red' }}></path> <path d='M0,3v-3l6,3l-6,3z' style={{ fill: 'red' }} />
</marker>
<marker id='arrow-off' viewBox='0 0 6 6' refX='3' refY='3' markerWidth='6' markerHeight='6' orient='auto'>
<path d='M0,3v-3l6,3l-6,3z' style={{ fill: 'black', fillOpacity: 0.2 }} />
</marker> </marker>
<marker id="arrow-off" viewBox="0 0 6 6" refX="3" refY="3" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,3v-3l6,3l-6,3z" style={{ fill: 'black', fillOpacity: 0.2 }}></path>
</marker>
</defs> </defs>
); )
export default MapDefsMarkers; export default MapDefsMarkers

View File

@@ -1,23 +1,23 @@
import React from 'react'; import React from 'react'
import { Portal } from 'react-portal'; import { Portal } from 'react-portal'
function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, narrative, onSelect, svg, locations }){ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, narrative, onSelect, svg, locations }) {
function getLocationEventsDistribution(location) { // function getLocationEventsDistribution (location) {
const eventCount = {}; // const eventCount = {}
const categories = categories; //
// categories.forEach(cat => {
// eventCount[cat.category] = []
// })
//
// location.events.forEach((event) => {
// ;
// eventCount[event.category].push(event)
// })
//
// return eventCount
// }
categories.forEach(cat => { function renderLocation (location) {
eventCount[cat.category] = [];
});
location.events.forEach((event) => {;
eventCount[event.category].push(event);
});
return eventCount;
}
function renderLocation(location) {
/** /**
{ {
events: [...], events: [...],
@@ -26,7 +26,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
longitude: '32.2' longitude: '32.2'
} }
*/ */
const { x, y } = projectPoint([location.latitude, location.longitude]); const { x, y } = projectPoint([location.latitude, location.longitude])
// const eventsByCategory = getLocationEventsDistribution(location); // const eventsByCategory = getLocationEventsDistribution(location);
const locCategory = location.events.length > 0 ? location.events[0].category : 'default' const locCategory = location.events.length > 0 ? location.events[0].category : 'default'
@@ -37,7 +37,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
const styles = ({ const styles = ({
fill: getCategoryColor(locCategory), fill: getCategoryColor(locCategory),
fillOpacity: 1, fillOpacity: 1,
...customStyles[0] ...extraStyles
}) })
// in narrative mode, only render events in narrative // in narrative mode, only render events in narrative
@@ -53,16 +53,15 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
return ( return (
<g <g
className="location" className='location'
transform={`translate(${x}, ${y})`} transform={`translate(${x}, ${y})`}
onClick={() => onSelect(location.events)} onClick={() => onSelect(location.events)}
> >
<circle <circle
className="location-event-marker" className='location-event-marker'
r={7} r={7}
style={styles} style={styles}
> />
</circle>
{extraRender ? extraRender() : null} {extraRender ? extraRender() : null}
</g> </g>
) )
@@ -72,7 +71,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
<Portal node={svg}> <Portal node={svg}>
{locations.map(renderLocation)} {locations.map(renderLocation)}
</Portal> </Portal>
); )
} }
export default MapEvents; export default MapEvents

View File

@@ -2,40 +2,39 @@ import React from 'react'
import { Portal } from 'react-portal' import { Portal } from 'react-portal'
function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) { function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) {
function getNarrativeStyle(narrativeId) { function getNarrativeStyle (narrativeId) {
const styleName = (narrativeId && narrativeId in styles) const styleName = (narrativeId && narrativeId in styles)
? narrativeId ? narrativeId
: 'default' : 'default'
return styles[styleName] return styles[styleName]
} }
function getStepStyle(name) { function getStepStyle (name) {
if (name === 'None') return null if (name === 'None') return null
return styles.stepStyles[name] return styles.stepStyles[name]
} }
function hasNoLocation(step) { function hasNoLocation (step) {
return (step.latitude === '' || step.longitude === '') return (step.latitude === '' || step.longitude === '')
} }
function renderNarrativeStep(idx, n) { function renderNarrativeStep (idx, n) {
const step = n.steps[idx] const step = n.steps[idx]
const step2 = n.steps[idx + 1] const step2 = n.steps[idx + 1]
// don't draw if one of the steps has no location // don't draw if one of the steps has no location
if (hasNoLocation(step) || hasNoLocation(step2)) if (hasNoLocation(step) || hasNoLocation(step2)) { return null }
return null
// 0 if not in narrative mode, 1 if active narrative, 0.1 if inactive // 0 if not in narrative mode, 1 if active narrative, 0.1 if inactive
let styles = { let styles = {
strokeOpacity: (n === null) ? 0 strokeOpacity: (n === null) ? 0
: (step && (n.id === narrative.id)) ? 1 : 0.1, : (step && (n.id === narrative.id)) ? 1 : 0.1,
strokeWidth: 0, strokeWidth: 0,
strokeDasharray: 'none', strokeDasharray: 'none',
stroke: 'none' stroke: 'none'
} }
const p1 = projectPoint([step.latitude, step.longitude]) const p1 = projectPoint([step.latitude, step.longitude])
const p2 = projectPoint([step2.latitude, step2.longitude]) const p2 = projectPoint([step2.latitude, step2.longitude])
if (step) { if (step) {
@@ -55,40 +54,37 @@ function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives,
...styles, ...styles,
...getNarrativeStyle(n.id) ...getNarrativeStyle(n.id)
} }
return _renderNarrativeStep(p1,p2,styles) return _renderNarrativeStep(p1, p2, styles)
} }
} }
} }
function _renderNarrativeStep(p1, p2, styles) { function _renderNarrativeStep (p1, p2, styles) {
const { stroke, strokeWidth, strokeDasharray, strokeOpacity } = styles const { stroke, strokeWidth, strokeDasharray, strokeOpacity } = styles
return ( return (
<line <line
className="narrative-step" className='narrative-step'
x1={p1.x} x1={p1.x}
x2={p2.x} x2={p2.x}
y1={p1.y} y1={p1.y}
y2={p2.y} y2={p2.y}
markerStart="none" markerStart='none'
onClick={() => onSelectNarrative(n)} onClick={n => onSelectNarrative(n)}
style={{ style={{
strokeWidth, strokeWidth,
strokeDasharray, strokeDasharray,
strokeOpacity, strokeOpacity,
stroke, stroke
}} }}
> />
</line>
) )
} }
function renderNarrative(n) { function renderNarrative (n) {
const steps = n.steps.slice(0, n.steps.length - 1) const steps = n.steps.slice(0, n.steps.length - 1)
return ( return (
<g id={`narrative-${n.id.replace(/ /g,"_")}`} className="narrative"> <g id={`narrative-${n.id.replace(/ /g, '_')}`} className='narrative'>
{steps.map((s, idx) => renderNarrativeStep(idx, n))} {steps.map((s, idx) => renderNarrativeStep(idx, n))}
</g> </g>
) )

View File

@@ -1,31 +1,30 @@
import React from 'react'; import React from 'react'
import { Portal } from 'react-portal'; import { Portal } from 'react-portal'
class MapSelectedEvents extends React.Component { class MapSelectedEvents extends React.Component {
renderMarker (event) { renderMarker (event) {
const { x, y } = this.props.projectPoint([event.latitude, event.longitude]); const { x, y } = this.props.projectPoint([event.latitude, event.longitude])
return ( return (
<g <g
className="location-marker" className='location-marker'
transform={`translate(${x - 32}, ${y})`} transform={`translate(${x - 32}, ${y})`}
> >
<path <path
className="leaflet-interactive" className='leaflet-interactive'
stroke="#ffffff" stroke='#ffffff'
stroke-opacity="1" stroke-opacity='1'
stroke-width="3" stroke-width='3'
stroke-linecap="" stroke-linecap=''
stroke-linejoin="round" stroke-linejoin='round'
stroke-dasharray="5,2" stroke-dasharray='5,2'
fill="none" fill='none'
d="M0,0a32,32 0 1,0 64,0 a32,32 0 1,0 -64,0 " d='M0,0a32,32 0 1,0 64,0 a32,32 0 1,0 -64,0 '
> />
</path>
</g> </g>
); )
} }
render() { render () {
return ( return (
<Portal node={this.props.svg}> <Portal node={this.props.svg}>
{this.props.selected.map(s => this.renderMarker(s))} {this.props.selected.map(s => this.renderMarker(s))}
@@ -33,4 +32,4 @@ class MapSelectedEvents extends React.Component {
) )
} }
} }
export default MapSelectedEvents; export default MapSelectedEvents

View File

@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import { Portal } from 'react-portal' import { Portal } from 'react-portal'
function MapShapes({ svg, shapes, projectPoint, styles }) { function MapShapes ({ svg, shapes, projectPoint, styles }) {
function renderShape(shape) { function renderShape (shape) {
const lineCoords = [] const lineCoords = []
const points = shape.points const points = shape.points
.map(projectPoint) .map(projectPoint)
points.forEach((p1, idx) => { points.forEach((p1, idx) => {
if (idx < shape.points.length - 1) { if (idx < shape.points.length - 1) {
const p2 = points[idx+1] const p2 = points[idx + 1]
lineCoords.push({ lineCoords.push({
x1: p1.x, x1: p1.x,
y1: p1.y, y1: p1.y,
@@ -27,11 +27,10 @@ function MapShapes({ svg, shapes, projectPoint, styles }) {
return ( return (
<line <line
id={`${shape.name}_style`} id={`${shape.name}_style`}
markerStart="none" markerStart='none'
{...coords} {...coords}
style={shapeStyles} style={shapeStyles}
> />
</line>
) )
}) })
} }
@@ -40,12 +39,11 @@ function MapShapes({ svg, shapes, projectPoint, styles }) {
return ( return (
<Portal node={svg}> <Portal node={svg}>
<g id={`shapes-layer`} className="narrative"> <g id={`shapes-layer`} className='narrative'>
{shapes.map(renderShape)} {shapes.map(renderShape)}
</g> </g>
</Portal> </Portal>
) )
} }
export default MapShapes export default MapShapes

View File

@@ -1,25 +1,24 @@
import React from 'react'; import React from 'react'
function MapSites({ sites, projectPoint }) { function MapSites ({ sites, projectPoint }) {
function renderSite(site) { function renderSite (site) {
const { x, y } = projectPoint([site.latitude, site.longitude]); const { x, y } = projectPoint([site.latitude, site.longitude])
return (<div return (<div
className="leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top" className='leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top'
style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)`}}> style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)` }}>
{site.site} {site.site}
</div> </div>
); )
} }
if (!sites || !sites.length) return null; if (!sites || !sites.length) return null
return ( return (
<div className="sites-layer"> <div className='sites-layer'>
{sites.map(renderSite)} {sites.map(renderSite)}
</div> </div>
) )
} }
export default MapSites; export default MapSites

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'; import { Provider } from 'react-redux'
import store from './store/index.js'; import store from './store/index.js'
import App from './components/App.jsx'; import App from './components/App.jsx'
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider>, </Provider>,
document.getElementById('explore-app') document.getElementById('explore-app')
); )