mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
lint jsx files
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"build": "NODE_ENV=production webpack --mode production",
|
||||
"test": "ava --verbose",
|
||||
"test-watch": "ava --watch",
|
||||
"lint": "standard \"src/**/*.js\" \"test/**/*.js\""
|
||||
"lint": "standard \"src/**/*.js\" \"src/**/*.jsx\" \"test/**/*.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import '../scss/main.scss';
|
||||
import React from 'react';
|
||||
import Dashboard from './Dashboard.jsx';
|
||||
import '../scss/main.scss'
|
||||
import React from 'react'
|
||||
import Dashboard from './Dashboard.jsx'
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
render() {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Dashboard />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
||||
@@ -1,59 +1,40 @@
|
||||
import copy from '../js/data/copy.json'
|
||||
import {
|
||||
isNotNullNorUndefined,
|
||||
parseDate,
|
||||
formatterWithYear
|
||||
} from '../js/utilities'
|
||||
import React from 'react'
|
||||
|
||||
import Spinner from './presentational/Spinner'
|
||||
import CardTimestamp from './presentational/Card/Timestamp'
|
||||
import CardLocation from './presentational/Card/Location'
|
||||
import CardCaret from './presentational/Card/Caret'
|
||||
import CardTags from './presentational/Card/Tags'
|
||||
import CardSummary from './presentational/Card/Summary'
|
||||
import CardSource from './presentational/Card/Source'
|
||||
import CardCategory from './presentational/Card/Category'
|
||||
import CardNarrative from './presentational/Card/Narrative'
|
||||
|
||||
class Card extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
toggle () {
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen
|
||||
})
|
||||
}
|
||||
|
||||
makeTimelabel(timestamp) {
|
||||
makeTimelabel (timestamp) {
|
||||
if (timestamp === null) return null
|
||||
const parsedTimestamp = parseDate(timestamp)
|
||||
const timelabel = formatterWithYear(parsedTimestamp)
|
||||
return timelabel
|
||||
}
|
||||
|
||||
renderCategory() {
|
||||
const categoryTitle = copy[this.props.language].cardstack.category
|
||||
const categoryLabel = this.props.event.category
|
||||
const color = this.props.getCategoryColor(this.props.event.category)
|
||||
|
||||
return null
|
||||
// return (
|
||||
// <CardCategory
|
||||
// categoryTitle={categoryTitle}
|
||||
// categoryLabel={categoryLabel}
|
||||
// color={color}
|
||||
// />
|
||||
// )
|
||||
}
|
||||
|
||||
renderSummary() {
|
||||
renderSummary () {
|
||||
return (
|
||||
<CardSummary
|
||||
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)) {
|
||||
return null
|
||||
}
|
||||
@@ -75,7 +56,7 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderLocation() {
|
||||
renderLocation () {
|
||||
return (
|
||||
<CardLocation
|
||||
language={this.props.language}
|
||||
@@ -84,15 +65,15 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderSources() {
|
||||
renderSources () {
|
||||
if (this.props.sourceError) {
|
||||
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 (
|
||||
<div className='card-col'>
|
||||
<h4>{source_lang}: </h4>
|
||||
<h4>{sourceLang}: </h4>
|
||||
{this.props.event.sources.map(source => (
|
||||
<CardSource
|
||||
isLoading={this.props.isLoading}
|
||||
@@ -105,7 +86,7 @@ class Card extends React.Component {
|
||||
}
|
||||
|
||||
// NB: should be internaionalized.
|
||||
renderTimestamp() {
|
||||
renderTimestamp () {
|
||||
return (
|
||||
<CardTimestamp
|
||||
makeTimelabel={(timestamp) => this.makeTimelabel(timestamp)}
|
||||
@@ -115,11 +96,10 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderNarrative() {
|
||||
renderNarrative () {
|
||||
const links = this.props.getNarrativeLinks(this.props.event)
|
||||
|
||||
if (links !== null) {
|
||||
|
||||
return (
|
||||
<CardNarrative
|
||||
select={(event) => this.props.onSelect([event])}
|
||||
@@ -131,7 +111,7 @@ class Card extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderMain() {
|
||||
renderMain () {
|
||||
return (
|
||||
<div className='card-container'>
|
||||
<div className='card-row details'>
|
||||
@@ -144,7 +124,7 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderExtra() {
|
||||
renderExtra () {
|
||||
return (
|
||||
<div className='card-bottomhalf'>
|
||||
{this.renderTags()}
|
||||
@@ -154,7 +134,7 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderCaret() {
|
||||
renderCaret () {
|
||||
return (
|
||||
<CardCaret
|
||||
toggle={() => this.toggle()}
|
||||
@@ -163,10 +143,10 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { isSelected } = this.props
|
||||
return (
|
||||
<li className={`event-card ${isSelected ? 'selected' : ''}`}>
|
||||
<li className={`event-card ${isSelected ? 'selected' : ''}`}>
|
||||
{this.renderMain()}
|
||||
{this.state.isOpen ? this.renderExtra() : null}
|
||||
{isSelected ? this.renderCaret() : null}
|
||||
|
||||
@@ -4,15 +4,11 @@ import * as selectors from '../selectors'
|
||||
|
||||
import Card from './Card.jsx'
|
||||
import copy from '../js/data/copy.json'
|
||||
import {
|
||||
isNotNullNorUndefined
|
||||
} from '../js/utilities.js'
|
||||
|
||||
class CardStack extends React.Component {
|
||||
renderCards(events, selections) {
|
||||
renderCards (events, selections) {
|
||||
// if no selections provided, select all
|
||||
if (!selections)
|
||||
selections = events.map(e => true)
|
||||
if (!selections) { selections = events.map(e => true) }
|
||||
|
||||
return events.map((event, idx) => (
|
||||
<Card
|
||||
@@ -32,7 +28,7 @@ class CardStack extends React.Component {
|
||||
))
|
||||
}
|
||||
|
||||
renderSelectedCards() {
|
||||
renderSelectedCards () {
|
||||
const { selected } = this.props
|
||||
if (selected.length > 0) {
|
||||
return this.renderCards(selected)
|
||||
@@ -40,7 +36,7 @@ class CardStack extends React.Component {
|
||||
return null
|
||||
}
|
||||
|
||||
renderNarrativeCards() {
|
||||
renderNarrativeCards () {
|
||||
const { narrative } = this.props
|
||||
const showing = narrative.steps.slice(narrative.current)
|
||||
const selections = showing
|
||||
@@ -49,8 +45,8 @@ class CardStack extends React.Component {
|
||||
return this.renderCards(showing, selections)
|
||||
}
|
||||
|
||||
renderCardStackHeader() {
|
||||
const header_lang = copy[this.props.language].cardstack.header
|
||||
renderCardStackHeader () {
|
||||
const headerLang = copy[this.props.language].cardstack.header
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -58,17 +54,17 @@ class CardStack extends React.Component {
|
||||
className='card-stack-header'
|
||||
onClick={() => this.props.onToggleCardstack()}
|
||||
>
|
||||
<button className="side-menu-burg is-active"><span></span></button>
|
||||
<p className="header-copy top">
|
||||
{`${this.props.selected.length} ${header_lang}`}
|
||||
<button className='side-menu-burg is-active'><span /></button>
|
||||
<p className='header-copy top'>
|
||||
{`${this.props.selected.length} ${headerLang}`}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderCardStackContent() {
|
||||
renderCardStackContent () {
|
||||
return (
|
||||
<div id="card-stack-content" className="card-stack-content">
|
||||
<div id='card-stack-content' className='card-stack-content'>
|
||||
<ul>
|
||||
{this.renderSelectedCards()}
|
||||
</ul>
|
||||
@@ -76,9 +72,9 @@ class CardStack extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderNarrativeContent() {
|
||||
renderNarrativeContent () {
|
||||
return (
|
||||
<div id="card-stack-content" className="card-stack-content">
|
||||
<div id='card-stack-content' className='card-stack-content'>
|
||||
<ul>
|
||||
{this.renderNarrativeCards()}
|
||||
</ul>
|
||||
@@ -86,14 +82,14 @@ class CardStack extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { isCardstack, selected, narrative } = this.props
|
||||
|
||||
if (selected.length > 0) {
|
||||
if (!narrative) {
|
||||
return (
|
||||
<div
|
||||
id="card-stack"
|
||||
id='card-stack'
|
||||
className={`card-stack
|
||||
${isCardstack ? '' : ' folded'}`
|
||||
}
|
||||
@@ -105,7 +101,7 @@ class CardStack extends React.Component {
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
id="card-stack"
|
||||
id='card-stack'
|
||||
className={`card-stack narrative-mode
|
||||
${isCardstack ? '' : ' folded'}`
|
||||
}
|
||||
@@ -116,11 +112,11 @@ class CardStack extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
return <div/>
|
||||
return <div />
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
narrative: selectors.selectActiveNarrative(state),
|
||||
selected: selectors.selectSelected(state),
|
||||
|
||||
@@ -14,10 +14,10 @@ import InfoPopUp from './InfoPopup.jsx'
|
||||
import Timeline from './Timeline.jsx'
|
||||
import Notification from './Notification.jsx'
|
||||
|
||||
import { parseDate, injectSource } from '../js/utilities'
|
||||
import { parseDate } from '../js/utilities'
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
constructor(props) {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleViewSource = this.handleViewSource.bind(this)
|
||||
@@ -30,28 +30,28 @@ class Dashboard extends React.Component {
|
||||
this.eventsById = {}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount () {
|
||||
if (!this.props.app.isMobile) {
|
||||
this.props.actions.fetchDomain()
|
||||
.then(domain => this.props.actions.updateDomain(domain))
|
||||
}
|
||||
}
|
||||
|
||||
handleHighlight(highlighted) {
|
||||
this.props.actions.updateHighlighted((highlighted) ? highlighted : null)
|
||||
handleHighlight (highlighted) {
|
||||
this.props.actions.updateHighlighted((highlighted) || null)
|
||||
}
|
||||
|
||||
getEventById(eventId) {
|
||||
getEventById (eventId) {
|
||||
if (this.eventsById[eventId]) return this.eventsById[eventId]
|
||||
this.eventsById[eventId] = this.props.domain.events.find(ev => ev.id === eventId)
|
||||
return this.eventsById[eventId]
|
||||
}
|
||||
|
||||
handleViewSource(source) {
|
||||
handleViewSource (source) {
|
||||
this.props.actions.updateSource(source)
|
||||
}
|
||||
|
||||
handleSelect(selected) {
|
||||
handleSelect (selected) {
|
||||
if (selected) {
|
||||
let eventsToSelect = selected.map(event => this.getEventById(event.id))
|
||||
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']
|
||||
}
|
||||
|
||||
getNarrativeLinks(event) {
|
||||
getNarrativeLinks (event) {
|
||||
const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative)
|
||||
if (narrative) return narrative.byId[event.id]
|
||||
return null
|
||||
}
|
||||
|
||||
setNarrative(narrative) {
|
||||
setNarrative (narrative) {
|
||||
// only handleSelect if narrative is not null
|
||||
if (!!narrative)
|
||||
this.handleSelect([ narrative.steps[0] ])
|
||||
if (narrative) { this.handleSelect([ narrative.steps[0] ]) }
|
||||
this.props.actions.updateNarrative(narrative)
|
||||
}
|
||||
|
||||
moveInNarrative(amt) {
|
||||
moveInNarrative (amt) {
|
||||
const { current } = this.props.app.narrativeState
|
||||
const { narrative } = this.props.app
|
||||
|
||||
@@ -91,7 +90,7 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { actions, app, domain, ui } = this.props
|
||||
return (
|
||||
<div>
|
||||
@@ -107,7 +106,7 @@ class Dashboard extends React.Component {
|
||||
methods={{
|
||||
onSelect: this.handleSelect,
|
||||
onSelectNarrative: this.setNarrative,
|
||||
getCategoryColor: this.getCategoryColor,
|
||||
getCategoryColor: this.getCategoryColor
|
||||
}}
|
||||
/>
|
||||
<Timeline
|
||||
@@ -126,7 +125,7 @@ class Dashboard extends React.Component {
|
||||
getCategoryColor={category => this.getCategoryColor(category)}
|
||||
/>
|
||||
<NarrativeControls
|
||||
narrative={!!app.narrative ? {
|
||||
narrative={app.narrative ? {
|
||||
...app.narrative,
|
||||
current: app.narrativeState.current
|
||||
} : null}
|
||||
@@ -150,7 +149,8 @@ class Dashboard extends React.Component {
|
||||
<SourceOverlay
|
||||
source={app.source}
|
||||
onCancel={() => {
|
||||
actions.updateSource(null)}
|
||||
actions.updateSource(null)
|
||||
}
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
@@ -163,7 +163,7 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch)
|
||||
}
|
||||
@@ -172,5 +172,5 @@ function mapDispatchToProps(dispatch) {
|
||||
export default connect(
|
||||
state => state,
|
||||
// state => injectSource("Youtube - Novodvirske Tank Separatist Patrol Video"),
|
||||
mapDispatchToProps,
|
||||
mapDispatchToProps
|
||||
)(Dashboard)
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const Icon = ({ iconType }) => {
|
||||
if (iconType === 'personas') {
|
||||
return (
|
||||
<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="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" />
|
||||
<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="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" />
|
||||
<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='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' />
|
||||
<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='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' />
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
} else if (iconType === 'tipos') {
|
||||
return (
|
||||
<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
|
||||
return (
|
||||
<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
|
||||
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"/>
|
||||
<circle cx="23" cy="17.288" r="11.975"/>
|
||||
<circle strokeDasharray="3, 4" cx="25.987" cy="26.926" r="11.976" />
|
||||
</svg>
|
||||
);
|
||||
c0.228,0,0.449-0.021,0.674-0.034' />
|
||||
<circle cx='23' cy='17.288' r='11.975' />
|
||||
<circle strokeDasharray='3, 4' cx='25.987' cy='26.926' r='11.976' />
|
||||
</svg>
|
||||
)
|
||||
} else if (iconType === 'hardware') {
|
||||
return (
|
||||
<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
|
||||
return (
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
C4.678,8.926,5.069,8.534,5.553,8.534L5.553,8.534z"/>
|
||||
</svg>
|
||||
);
|
||||
C4.678,8.926,5.069,8.534,5.553,8.534L5.553,8.534z' />
|
||||
</svg>
|
||||
)
|
||||
} else if (iconType === 'escenas') {
|
||||
return (
|
||||
<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
|
||||
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
|
||||
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
|
||||
L20.5,2.537L20.5,2.537z" />
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<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
|
||||
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
|
||||
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
|
||||
L20.5,2.537L20.5,2.537z' />
|
||||
</svg>
|
||||
)
|
||||
} else if (iconType === 'docs') {
|
||||
return (
|
||||
<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
|
||||
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
|
||||
return (
|
||||
<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
|
||||
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
|
||||
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"/>
|
||||
<path d="M58.755,29.633"/>
|
||||
<path d="M21.86,40.072"/>
|
||||
<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="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="23.955" x2="31.577" y2="23.955"/>
|
||||
</svg>
|
||||
)
|
||||
c1.103,0,1.198,0.095,1.198,1.197V36.866z' />
|
||||
<path d='M58.755,29.633' />
|
||||
<path d='M21.86,40.072' />
|
||||
<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='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='23.955' x2='31.577' y2='23.955' />
|
||||
</svg>
|
||||
)
|
||||
} else if (iconType === 'search') {
|
||||
return (
|
||||
<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"/>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M28.24,28.24
|
||||
l8.346,8.346L28.24,28.24z"/>
|
||||
<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' />
|
||||
<path strokeLinecap='round' strokeLinejoin='round' d='M28.24,28.24
|
||||
l8.346,8.346L28.24,28.24z' />
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
export default Icon
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import React from 'react';
|
||||
import copy from '../js/data/copy.json';
|
||||
import React from 'react'
|
||||
import copy from '../js/data/copy.json'
|
||||
// NB: should we make this componetn part of a future feature?
|
||||
|
||||
export default class InfoPopUp extends React.Component{
|
||||
|
||||
renderView2DCopy() {
|
||||
return copy[this.props.app.language].legend.view2d.paragraphs.map(paragraph => <p>{paragraph}</p>);
|
||||
export default class InfoPopUp extends React.Component {
|
||||
renderView2DCopy () {
|
||||
return copy[this.props.app.language].legend.view2d.paragraphs.map(paragraph => <p>{paragraph}</p>)
|
||||
}
|
||||
|
||||
renderCategoryColors() {
|
||||
const colors = copy[this.props.app.language].legend.view2d.colors.slice(0);
|
||||
colors.reverse();
|
||||
renderCategoryColors () {
|
||||
const colors = copy[this.props.app.language].legend.view2d.colors.slice(0)
|
||||
colors.reverse()
|
||||
return (
|
||||
<div className="legend-labels" style={{ 'margin-left': '-10px' }}>
|
||||
<div className='legend-labels' style={{ 'margin-left': '-10px' }}>
|
||||
{colors.map((color, idx) => {
|
||||
return (
|
||||
<div className="label" style={{ 'margin-left': `${idx*5}` }}>
|
||||
<div className={`color-category ${color.class}`}></div>
|
||||
<div className='label' style={{ 'margin-left': `${idx * 5}` }}>
|
||||
<div className={`color-category ${color.class}`} />
|
||||
{color.label}
|
||||
</div>
|
||||
)
|
||||
@@ -25,51 +24,51 @@ export default class InfoPopUp extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
renderView2DLegend() {
|
||||
renderView2DLegend () {
|
||||
return (
|
||||
<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()}
|
||||
<div className="legend">
|
||||
<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">
|
||||
<circle fill="#D2CD28" cx="50" cy="50" r="50"/>
|
||||
<circle fill="#662770" cx="50" cy="50" r="40"/>
|
||||
<circle fill="#2F409A" cx="50" cy="50" r="30"/>
|
||||
<circle fill="#256C36" cx="50" cy="50" r="20"/>
|
||||
<circle fill="#FF0000" cx="50" cy="50" r="10"/>
|
||||
<div className='legend'>
|
||||
<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'>
|
||||
<circle fill='#D2CD28' cx='50' cy='50' r='50' />
|
||||
<circle fill='#662770' cx='50' cy='50' r='40' />
|
||||
<circle fill='#2F409A' cx='50' cy='50' r='30' />
|
||||
<circle fill='#256C36' cx='50' cy='50' r='20' />
|
||||
<circle fill='#FF0000' cx='50' cy='50' r='10' />
|
||||
</svg>
|
||||
{this.renderCategoryColors()}
|
||||
</div>
|
||||
<div className="legend-section">
|
||||
<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"/>
|
||||
<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"/>
|
||||
<div className='legend-section'>
|
||||
<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' />
|
||||
<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' />
|
||||
</svg>
|
||||
<div className="legend-labels">
|
||||
<div className="label">Comunicaciones</div>
|
||||
<div className='legend-labels'>
|
||||
<div className='label'>Comunicaciones</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="legend-section">
|
||||
<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"/>
|
||||
<div className='legend-section'>
|
||||
<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' />
|
||||
</svg>
|
||||
<div className="legend-labels">
|
||||
<div className="label">Ataques</div>
|
||||
<div className='legend-labels'>
|
||||
<div className='label'>Ataques</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="legend-section">
|
||||
<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="
|
||||
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="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="50.06" x2="84.195" y2="46"/>
|
||||
<div className='legend-section'>
|
||||
<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='
|
||||
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='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='50.06' x2='84.195' y2='46' />
|
||||
</svg>
|
||||
<div className="legend-labels">
|
||||
<div className="label">Rutas de bus</div>
|
||||
<div className='legend-labels'>
|
||||
<div className='label'>Rutas de bus</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,7 +76,7 @@ export default class InfoPopUp extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
return (
|
||||
<div>{this.renderView2DLegend()}</div>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global L */
|
||||
import React from 'react'
|
||||
import { Portal } from 'react-portal'
|
||||
|
||||
@@ -7,8 +8,6 @@ import * as selectors from '../selectors'
|
||||
import hash from 'object-hash'
|
||||
import 'leaflet'
|
||||
|
||||
import { isNotNullNorUndefined } from '../js/utilities'
|
||||
|
||||
import Sites from './presentational/Map/Sites.jsx'
|
||||
import Shapes from './presentational/Map/Shapes.jsx'
|
||||
import Events from './presentational/Map/Events.jsx'
|
||||
@@ -21,7 +20,7 @@ const supportedMapboxMap = ['streets', 'satellite']
|
||||
const defaultToken = 'your_token'
|
||||
|
||||
class Map extends React.Component {
|
||||
constructor() {
|
||||
constructor () {
|
||||
super()
|
||||
this.projectPoint = this.projectPoint.bind(this)
|
||||
this.svgRef = React.createRef()
|
||||
@@ -33,18 +32,18 @@ class Map extends React.Component {
|
||||
this.styleLocation = this.styleLocation.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount () {
|
||||
if (this.map === null) {
|
||||
this.initializeMap()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentWillReceiveProps (nextProps) {
|
||||
// Set appropriate zoom for narrative
|
||||
const { bounds } = nextProps.app.map
|
||||
if (hash(bounds) !== hash(this.props.app.map.bounds)
|
||||
&& bounds !== null) {
|
||||
this.map.fitBounds(bounds)
|
||||
if (hash(bounds) !== hash(this.props.app.map.bounds) &&
|
||||
bounds !== null) {
|
||||
this.map.fitBounds(bounds)
|
||||
} else {
|
||||
if (hash(nextProps.app.selected) !== hash(this.props.app.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
|
||||
*/
|
||||
@@ -69,22 +68,22 @@ class Map extends React.Component {
|
||||
.setMaxZoom(mapConf.maxZoom)
|
||||
.setMaxBounds(mapConf.maxBounds)
|
||||
|
||||
let s
|
||||
let firstLayer
|
||||
|
||||
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}`
|
||||
)
|
||||
} 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}`
|
||||
)
|
||||
} else {
|
||||
s = L.tileLayer(
|
||||
firstLayer = L.tileLayer(
|
||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||
)
|
||||
}
|
||||
s = s.addTo(map)
|
||||
firstLayer.addTo(map)
|
||||
|
||||
map.keyboard.disable()
|
||||
|
||||
@@ -96,7 +95,7 @@ class Map extends React.Component {
|
||||
this.map = map
|
||||
}
|
||||
|
||||
alignLayers() {
|
||||
alignLayers () {
|
||||
const mapNode = document.querySelector('.leaflet-map-pane')
|
||||
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])
|
||||
return {
|
||||
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()
|
||||
|
||||
return {
|
||||
@@ -130,7 +129,7 @@ class Map extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderTiles() {
|
||||
renderTiles () {
|
||||
const pane = this.map.getPanes().overlayPane
|
||||
const { width, height } = this.getClientDims()
|
||||
|
||||
@@ -140,15 +139,14 @@ class Map extends React.Component {
|
||||
ref={this.svgRef}
|
||||
width={width}
|
||||
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'
|
||||
>
|
||||
</svg>
|
||||
/>
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
||||
renderSites() {
|
||||
renderSites () {
|
||||
return (
|
||||
<Sites
|
||||
sites={this.props.domain.sites}
|
||||
@@ -158,7 +156,7 @@ class Map extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderShapes() {
|
||||
renderShapes () {
|
||||
return (
|
||||
<Shapes
|
||||
svg={this.svgRef.current}
|
||||
@@ -169,7 +167,7 @@ class Map extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderNarratives() {
|
||||
renderNarratives () {
|
||||
return (
|
||||
<Narratives
|
||||
svg={this.svgRef.current}
|
||||
@@ -192,7 +190,7 @@ class Map extends React.Component {
|
||||
* at the second index is an optional function that renders additional
|
||||
* components in the <g/> div.
|
||||
*/
|
||||
styleLocation(location) {
|
||||
styleLocation (location) {
|
||||
const noEvents = location.events.length
|
||||
return [
|
||||
null,
|
||||
@@ -200,7 +198,7 @@ class Map extends React.Component {
|
||||
]
|
||||
}
|
||||
|
||||
renderEvents() {
|
||||
renderEvents () {
|
||||
return (
|
||||
<Events
|
||||
svg={this.svgRef.current}
|
||||
@@ -216,7 +214,7 @@ class Map extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderSelected() {
|
||||
renderSelected () {
|
||||
return (
|
||||
<SelectedEvents
|
||||
svg={this.svgRef.current}
|
||||
@@ -226,8 +224,7 @@ class Map extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
renderMarkers() {
|
||||
renderMarkers () {
|
||||
return (
|
||||
<Portal node={this.svgRef.current}>
|
||||
<DefsMarkers />
|
||||
@@ -235,11 +232,10 @@ class Map extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { isShowingSites } = this.props.app.flags
|
||||
const classes = this.props.app.narrative ? 'map-wrapper narrative-mode' : 'map-wrapper'
|
||||
const innerMap = !!this.map ? (
|
||||
const innerMap = this.map ? (
|
||||
<React.Fragment>
|
||||
{this.renderTiles()}
|
||||
{this.renderMarkers()}
|
||||
@@ -260,7 +256,7 @@ class Map extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
domain: {
|
||||
locations: selectors.selectLocations(state),
|
||||
@@ -289,4 +285,3 @@ function mapStateToProps(state) {
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Map)
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import React from 'react'
|
||||
/* global fetch */
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import marked from 'marked'
|
||||
|
||||
class Md extends React.Component {
|
||||
constructor(props) {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { md: null, error: null }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount () {
|
||||
fetch(this.props.path)
|
||||
.then(resp => resp.text())
|
||||
.then(text => {
|
||||
if (text.length <= 0)
|
||||
throw new Error()
|
||||
if (text.length <= 0) { throw new Error() }
|
||||
|
||||
this.setState({ md: marked(text) })
|
||||
})
|
||||
.catch(err => {
|
||||
.catch(() => {
|
||||
this.setState({ error: true })
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
if (this.state.md && !this.state.error) {
|
||||
return (
|
||||
<div className="md-container" dangerouslySetInnerHTML={{ __html: this.state.md }} />
|
||||
<div className='md-container' dangerouslySetInnerHTML={{ __html: this.state.md }} />
|
||||
)
|
||||
} else if (this.state.error) {
|
||||
return this.props.unloader || <div>Error: couldn't load source</div>
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
export default class Notification extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super();
|
||||
export default class Notification extends React.Component {
|
||||
constructor (props) {
|
||||
super()
|
||||
this.state = {
|
||||
isExtended: false
|
||||
}
|
||||
}
|
||||
|
||||
toggleDetails() {
|
||||
this.setState({ isExtended: !this.state.isExtended });
|
||||
toggleDetails () {
|
||||
this.setState({ isExtended: !this.state.isExtended })
|
||||
}
|
||||
|
||||
renderItems(items) {
|
||||
if (!items) return '';
|
||||
renderItems (items) {
|
||||
if (!items) return ''
|
||||
return (
|
||||
<div>
|
||||
{items.map((item) => {
|
||||
if (item.error) {
|
||||
return (<p>{item.error.message}</p>);
|
||||
return (<p>{item.error.message}</p>)
|
||||
}
|
||||
return '';
|
||||
return ''
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderNotificationContent(notification) {
|
||||
let { type, message, items } = notification;
|
||||
renderNotificationContent (notification) {
|
||||
let { type, message, items } = notification
|
||||
|
||||
return (
|
||||
<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))
|
||||
if (notificationsToRender.length > 0) {
|
||||
return (
|
||||
<div className={`notification-wrapper`}>
|
||||
{this.props.notifications.map((notification) => {
|
||||
return (
|
||||
<div className='notification' onClick={() => this.toggleDetails() }>
|
||||
<div className='notification' onClick={() => this.toggleDetails()}>
|
||||
<button
|
||||
onClick={this.props.onToggle}
|
||||
className="side-menu-burg over-white is-active"
|
||||
className='side-menu-burg over-white is-active'
|
||||
>
|
||||
<span />
|
||||
</button>
|
||||
{this.renderNotificationContent(notification)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (<div/>);
|
||||
return (<div />)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import React from 'react';
|
||||
import copy from '../js/data/copy.json';
|
||||
import TagFilter from './TagFilter.jsx';
|
||||
/* global fetch */
|
||||
import React from 'react'
|
||||
import copy from '../js/data/copy.json'
|
||||
import TagFilter from './TagFilter.jsx'
|
||||
|
||||
export default class Search extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
searchValue: undefined,
|
||||
searchResults: []
|
||||
}
|
||||
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this);
|
||||
this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this)
|
||||
this.handleSearchSubmit = this.handleSearchSubmit.bind(this)
|
||||
}
|
||||
|
||||
handleSearchSubmit(e) {
|
||||
e.preventDefault();
|
||||
handleSearchSubmit (e) {
|
||||
e.preventDefault()
|
||||
fetch(`api/search/${this.state.searchValue}`)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
this.setState({
|
||||
searchResults: json.tags
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchChange(event) {
|
||||
this.setState({ searchValue: event.target.value });
|
||||
handleSearchChange (event) {
|
||||
this.setState({ searchValue: event.target.value })
|
||||
}
|
||||
|
||||
renderSearchResults() {
|
||||
renderSearchResults () {
|
||||
return (
|
||||
this.state.searchResults.map(tag => {
|
||||
return (
|
||||
<TagFilter
|
||||
isShowTree={true}
|
||||
isShowTree
|
||||
tags={this.props.tags}
|
||||
categories={this.props.categories}
|
||||
tagFilters={this.props.tagFilters}
|
||||
@@ -43,22 +44,22 @@ export default class Search extends React.Component {
|
||||
tag={tag}
|
||||
isCategory={this.props.isCategory}
|
||||
/>
|
||||
);
|
||||
)
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
return (
|
||||
<div className="search-content">
|
||||
<div className='search-content'>
|
||||
<h2>{copy[this.props.language].toolbar.panels.search.title}</h2>
|
||||
<form onSubmit={this.handleSearchSubmit}>
|
||||
<input
|
||||
value={this.state.searchValue}
|
||||
onChange={this.handleSearchChange}
|
||||
autoFocus
|
||||
type="text"
|
||||
name="search-input"
|
||||
type='text'
|
||||
name='search-input'
|
||||
placeholder={copy[this.props.language].toolbar.panels.search.placeholder}
|
||||
/>
|
||||
</form>
|
||||
@@ -66,6 +67,6 @@ export default class Search extends React.Component {
|
||||
{this.renderSearchResults()}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import NoSource from './presentational/NoSource'
|
||||
// TODO: move render functions into presentational components
|
||||
|
||||
class SourceOverlay extends React.Component {
|
||||
|
||||
constructor() {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.state = {
|
||||
@@ -16,29 +15,29 @@ class SourceOverlay extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderError() {
|
||||
renderError () {
|
||||
return (
|
||||
<NoSource failedUrls={["NOT ALL SOURCES AVAILABLE IN APPLICATION YET"]} />
|
||||
<NoSource failedUrls={['NOT ALL SOURCES AVAILABLE IN APPLICATION YET']} />
|
||||
)
|
||||
}
|
||||
|
||||
renderImage(path) {
|
||||
renderImage (path) {
|
||||
return (
|
||||
<div className='source-image-container'>
|
||||
<Img
|
||||
className='source-image'
|
||||
src={path}
|
||||
loader={<div style={{ width: '400px', height: '400px' }}><Spinner /></div>}
|
||||
unloader={<NoSource failedUrls={this.props.source.paths} />}
|
||||
/>
|
||||
<Img
|
||||
className='source-image'
|
||||
src={path}
|
||||
loader={<div style={{ width: '400px', height: '400px' }}><Spinner /></div>}
|
||||
unloader={<NoSource failedUrls={this.props.source.paths} />}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderVideo(path) {
|
||||
renderVideo (path) {
|
||||
// NB: assume only one video
|
||||
return (
|
||||
<div className="media-player">
|
||||
<div className='media-player'>
|
||||
<Player
|
||||
className='source-video'
|
||||
playsInline
|
||||
@@ -48,7 +47,7 @@ class SourceOverlay extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderText(path) {
|
||||
renderText (path) {
|
||||
return (
|
||||
<div className='source-text-container'>
|
||||
<Md
|
||||
@@ -60,15 +59,14 @@ class SourceOverlay extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
renderNoSupport(ext) {
|
||||
renderNoSupport (ext) {
|
||||
return (
|
||||
<NoSource failedUrls={[`Application does not support extension: ${ext}`]} />
|
||||
)
|
||||
}
|
||||
|
||||
toMedia(path) {
|
||||
let type;
|
||||
toMedia (path) {
|
||||
let type
|
||||
switch (true) {
|
||||
case /\.(png|jpg)$/.test(path):
|
||||
type = 'Image'; break
|
||||
@@ -82,7 +80,7 @@ class SourceOverlay extends React.Component {
|
||||
return { type, path }
|
||||
}
|
||||
|
||||
getTypeCounts(media) {
|
||||
getTypeCounts (media) {
|
||||
let counts = { Image: 0, Video: 0, Text: 0 }
|
||||
media.forEach(m => {
|
||||
counts[m.type] += 1
|
||||
@@ -90,7 +88,7 @@ class SourceOverlay extends React.Component {
|
||||
return counts
|
||||
}
|
||||
|
||||
_renderPath(media) {
|
||||
_renderPath (media) {
|
||||
const { path, type } = media
|
||||
switch (type) {
|
||||
case 'Image':
|
||||
@@ -104,101 +102,99 @@ class SourceOverlay extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
_renderCounts(counts) {
|
||||
_renderCounts (counts) {
|
||||
const strFor = type =>
|
||||
counts[type] > 0 ?
|
||||
`${counts[type]} ${type.toLowerCase()}${counts[type] > 1 ? 's': ''}`
|
||||
: ''
|
||||
counts[type] > 0
|
||||
? `${counts[type]} ${type.toLowerCase()}${counts[type] > 1 ? 's' : ''}`
|
||||
: ''
|
||||
const img = strFor('Image')
|
||||
const vid = strFor('Video')
|
||||
const txt = strFor('Text')
|
||||
|
||||
return (
|
||||
<div>
|
||||
{img ? img : ''}
|
||||
{(img && vid) ? `, ${vid}`: (vid || '')}
|
||||
{((img || vid) && txt) ? `, ${txt}`: (txt || '')}
|
||||
{img || ''}
|
||||
{(img && vid) ? `, ${vid}` : (vid || '')}
|
||||
{((img || vid) && txt) ? `, ${txt}` : (txt || '')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
_renderContent(media) {
|
||||
const el = document.querySelector(`.source-media-gallery`);
|
||||
const shiftW = (!!el) ? el.getBoundingClientRect().width : 0;
|
||||
_renderContent (media) {
|
||||
const el = document.querySelector(`.source-media-gallery`)
|
||||
const shiftW = (el) ? el.getBoundingClientRect().width : 0
|
||||
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))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
onShiftGallery(shift) {
|
||||
if (this.state.idx === 0 && shift === -1) return;
|
||||
onShiftGallery (shift) {
|
||||
if (this.state.idx === 0 && 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) {
|
||||
return (
|
||||
<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="next" onClick={() => this.onShiftGallery(1)}><svg><path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z"></path></svg></div>
|
||||
<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' /></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>
|
||||
);
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="media-gallery-controls"></div>
|
||||
);
|
||||
<div className='media-gallery-controls' />
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
if (typeof(this.props.source) !== 'object') {
|
||||
if (typeof (this.props.source) !== 'object') {
|
||||
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 counts = this.getTypeCounts(media)
|
||||
|
||||
return (
|
||||
<div className="mo-overlay" onClick={this.props.onCancel}>
|
||||
<div className="mo-container" onClick={(e) => { e.stopPropagation(); }}>
|
||||
<div className="mo-header">
|
||||
<div className="mo-header-close" onClick={this.props.onCancel}>
|
||||
<button className="side-menu-burg is-active"><span></span></button>
|
||||
<div className='mo-overlay' onClick={this.props.onCancel}>
|
||||
<div className='mo-container' onClick={(e) => { e.stopPropagation() }}>
|
||||
<div className='mo-header'>
|
||||
<div className='mo-header-close' onClick={this.props.onCancel}>
|
||||
<button className='side-menu-burg is-active'><span /></button>
|
||||
</div>
|
||||
<div className="mo-header-text">{this.props.source.title}</div>
|
||||
<div className='mo-header-text'>{this.props.source.title}</div>
|
||||
</div>
|
||||
<div className="mo-media-container">
|
||||
<div className='mo-media-container'>
|
||||
{this._renderContent(media)}
|
||||
{this._renderControls()}
|
||||
</div>
|
||||
<div className="mo-meta-container">
|
||||
<div className="mo-box-title">
|
||||
<div className='mo-meta-container'>
|
||||
<div className='mo-box-title'>
|
||||
{/* <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>
|
||||
|
||||
<div className="mo-box">
|
||||
<div className='mo-box'>
|
||||
<div>
|
||||
{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>
|
||||
{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>
|
||||
{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>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,44 @@
|
||||
import React from 'react';
|
||||
import Checkbox from './presentational/Checkbox';
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
|
||||
class TagFilter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
isActive() {
|
||||
isActive () {
|
||||
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()) {
|
||||
this.props.filter({
|
||||
tags: this.props.tagFilters.filter(element => element !== this.props.tag.id)
|
||||
});
|
||||
})
|
||||
} else {
|
||||
this.props.filter({
|
||||
tags: this.props.tagFilters.concat(this.props.tag.id)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onClickCategory() {
|
||||
onClickCategory () {
|
||||
if (this.isActive()) {
|
||||
this.props.filter({
|
||||
categories: this.props.categoryFilters.filter(element => element !== this.props.tag.id)
|
||||
});
|
||||
})
|
||||
} else {
|
||||
this.props.filter({
|
||||
categories: this.props.categoryFilters.concat(this.props.tag.id)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
renderTag() {
|
||||
const tag = this.props.tag;
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter';
|
||||
let label = `${tag.name} ( ${tag.mentions} )`;
|
||||
renderTag () {
|
||||
const tag = this.props.tag
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
let label = `${tag.name} ( ${tag.mentions} )`
|
||||
if (this.props.isShowTree) {
|
||||
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`;
|
||||
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`
|
||||
}
|
||||
return (
|
||||
<li
|
||||
@@ -55,12 +51,12 @@ class TagFilter extends React.Component {
|
||||
onClickCheckbox={() => this.onClickTag()}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
renderCategory() {
|
||||
const category = this.props.categories[this.props.tag.id];
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter';
|
||||
renderCategory () {
|
||||
const category = this.props.categories[this.props.tag.id]
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
|
||||
if (category) {
|
||||
return (
|
||||
@@ -74,15 +70,15 @@ class TagFilter extends React.Component {
|
||||
onClickCheckbox={() => this.onClickCategory()}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
)
|
||||
}
|
||||
return (<div/>);
|
||||
return (<div />)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isCategory) return (this.renderCategory());
|
||||
return (this.renderTag());
|
||||
render () {
|
||||
if (this.props.isCategory) return (this.renderCategory())
|
||||
return (this.renderTag())
|
||||
}
|
||||
}
|
||||
|
||||
export default TagFilter;
|
||||
export default TagFilter
|
||||
|
||||
@@ -1,68 +1,67 @@
|
||||
import React from 'react';
|
||||
import Checkbox from './presentational/Checkbox';
|
||||
import copy from '../js/data/copy.json';
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
import copy from '../js/data/copy.json'
|
||||
|
||||
class TagListPanel extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
treeComponents: []
|
||||
}
|
||||
this.treeComponents = [];
|
||||
this.newTagFilters = [];
|
||||
this.treeComponents = []
|
||||
this.newTagFilters = []
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.computeTree(this.props.tags);//.children[this.props.tagType]);
|
||||
componentDidMount () {
|
||||
this.computeTree(this.props.tags)// .children[this.props.tagType]);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.computeTree(nextProps.tags);//.children[nextProps.tagType]);
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.computeTree(nextProps.tags)// .children[nextProps.tagType]);
|
||||
}
|
||||
|
||||
onClickCheckbox(obj, type) {
|
||||
onClickCheckbox (obj, type) {
|
||||
obj.active = !obj.active
|
||||
if (type === 'category') this.props.onCategoryFilter(obj);
|
||||
if (type === 'tag') this.props.onTagFilter(obj);
|
||||
if (type === 'category') this.props.onCategoryFilter(obj)
|
||||
if (type === 'tag') this.props.onTagFilter(obj)
|
||||
}
|
||||
|
||||
createNodeComponent (node, depth) {
|
||||
return (
|
||||
<li
|
||||
key={node.key.replace(/ /g,"_")}
|
||||
key={node.key.replace(/ /g, '_')}
|
||||
className={'tag-filter active'}
|
||||
style={{ marginLeft: `${depth*20}px` }}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
>
|
||||
<Checkbox
|
||||
label={node.key}
|
||||
isActive={node.active}
|
||||
onClickCheckbox={() => this.onClickCheckbox(node, 'tag')}
|
||||
/>
|
||||
<Checkbox
|
||||
label={node.key}
|
||||
isActive={node.active}
|
||||
onClickCheckbox={() => this.onClickCheckbox(node, 'tag')}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
traverseNodeAndCreateComponent(node, depth) {
|
||||
traverseNodeAndCreateComponent (node, depth) {
|
||||
// add and create node component
|
||||
const newComponent = this.createNodeComponent(node, depth);
|
||||
const newComponent = this.createNodeComponent(node, depth)
|
||||
this.treeComponents.push(newComponent)
|
||||
depth = depth + 1;
|
||||
depth = depth + 1
|
||||
if (Object.keys(node.children).length > 0) {
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
this.traverseNodeAndCreateComponent(childNode, depth);
|
||||
});
|
||||
this.traverseNodeAndCreateComponent(childNode, depth)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
computeTree (node) {
|
||||
this.treeComponents = [];
|
||||
let depth = 0;
|
||||
this.traverseNodeAndCreateComponent(node, depth);
|
||||
this.setState({ treeComponents: this.treeComponents });
|
||||
this.treeComponents = []
|
||||
let depth = 0
|
||||
this.traverseNodeAndCreateComponent(node, depth)
|
||||
this.setState({ treeComponents: this.treeComponents })
|
||||
}
|
||||
|
||||
renderTree() {
|
||||
renderTree () {
|
||||
return (
|
||||
<div>
|
||||
<h2>{copy[this.props.language].toolbar.tags}</h2>
|
||||
@@ -71,13 +70,13 @@ class TagListPanel extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderCategoryTree() {
|
||||
renderCategoryTree () {
|
||||
return (
|
||||
<div>
|
||||
<h2>{copy[this.props.language].toolbar.categories}</h2>
|
||||
{this.props.categories.map(cat => {
|
||||
return (<li
|
||||
key={cat.category.replace(/ /g,"_")}
|
||||
key={cat.category.replace(/ /g, '_')}
|
||||
className={'tag-filter active'}
|
||||
style={{ marginLeft: '20px' }}
|
||||
>
|
||||
@@ -87,22 +86,22 @@ class TagListPanel extends React.Component {
|
||||
onClickCheckbox={() => this.onClickCheckbox(cat, 'category')}
|
||||
/>
|
||||
</li>)
|
||||
})
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
return (
|
||||
<div className="react-innertabpanel">
|
||||
<div className='react-innertabpanel'>
|
||||
<h2>{copy[this.props.language].toolbar.explore_by_tag__title}</h2>
|
||||
<p>{copy[this.props.language].toolbar.explore_by_tag__description}</p>
|
||||
{this.renderCategoryTree()}
|
||||
{this.renderTree()}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TagListPanel;
|
||||
export default TagListPanel
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global d3 */
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import * as selectors from '../selectors'
|
||||
@@ -10,13 +11,12 @@ import Axis from './TimelineAxis.jsx'
|
||||
import Clip from './presentational/Timeline/Clip'
|
||||
import Handles from './presentational/Timeline/Handles.js'
|
||||
import ZoomControls from './presentational/Timeline/ZoomControls.js'
|
||||
import Labels from './presentational/Timeline/Labels.js'
|
||||
import Markers from './presentational/Timeline/Markers.js'
|
||||
import Events from './presentational/Timeline/Events.js'
|
||||
import Categories from './TimelineCategories.jsx'
|
||||
|
||||
class Timeline extends React.Component {
|
||||
constructor(props) {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.styleDatetime = this.styleDatetime.bind(this)
|
||||
this.getDatetimeX = this.getDatetimeX.bind(this)
|
||||
@@ -33,12 +33,12 @@ class Timeline extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount () {
|
||||
this.computeDims()
|
||||
this.addEventListeners()
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (hash(nextProps) !== hash(this.props)) {
|
||||
this.setState({
|
||||
timerange: nextProps.app.timeline.range,
|
||||
@@ -59,22 +59,21 @@ class Timeline extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
addEventListeners () {
|
||||
window.addEventListener('resize', () => { this.computeDims() })
|
||||
let element = document.querySelector('.timeline-wrapper')
|
||||
element.addEventListener("transitionend", (event) => {
|
||||
element.addEventListener('transitionend', (event) => {
|
||||
this.computeDims()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
makeScaleX() {
|
||||
makeScaleX () {
|
||||
return d3.scaleTime()
|
||||
.domain(this.state.timerange)
|
||||
.range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls])
|
||||
.domain(this.state.timerange)
|
||||
.range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls])
|
||||
}
|
||||
|
||||
makeScaleY(categories) {
|
||||
makeScaleY (categories) {
|
||||
const tickHeight = 15
|
||||
const catsYpos = categories.map((g, i) => (i + 1) * this.state.dims.trackHeight / categories.length + tickHeight / 2)
|
||||
return d3.scaleOrdinal()
|
||||
@@ -82,7 +81,7 @@ class Timeline extends React.Component {
|
||||
.range(catsYpos)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.timerange !== this.state.timerange) {
|
||||
this.setState({ scaleX: this.makeScaleX() })
|
||||
}
|
||||
@@ -91,19 +90,19 @@ class Timeline extends React.Component {
|
||||
/**
|
||||
* Returns the time scale (x) extent in minutes
|
||||
*/
|
||||
getTimeScaleExtent() {
|
||||
getTimeScaleExtent () {
|
||||
if (!this.state.scaleX) return 0
|
||||
const timeDomain = this.state.scaleX.domain()
|
||||
return (timeDomain[1].getTime() - timeDomain[0].getTime()) / 60000
|
||||
}
|
||||
|
||||
onClickArrow() {
|
||||
onClickArrow () {
|
||||
this.setState((prevState, props) => {
|
||||
return {isFolded: !prevState.isFolded}
|
||||
return { isFolded: !prevState.isFolded }
|
||||
})
|
||||
}
|
||||
|
||||
computeDims() {
|
||||
computeDims () {
|
||||
const dom = this.props.ui.dom.timeline
|
||||
if (document.querySelector(`#${dom}`) !== null) {
|
||||
const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect()
|
||||
@@ -114,18 +113,18 @@ class Timeline extends React.Component {
|
||||
width: boundingClient.width
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.setState({ scaleX: this.makeScaleX()
|
||||
})
|
||||
() => {
|
||||
this.setState({ scaleX: this.makeScaleX()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Shift time range by moving forward or backwards
|
||||
* @param {String} direction: 'forward' / 'backwards'
|
||||
*/
|
||||
onMoveTime(direction) {
|
||||
onMoveTime (direction) {
|
||||
this.props.methods.onSelect()
|
||||
const extent = this.getTimeScaleExtent()
|
||||
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 domain0 = d3.timeMinute.offset(newCentralTime, -extent/2)
|
||||
const domainF = d3.timeMinute.offset(newCentralTime, +extent/2)
|
||||
const domain0 = d3.timeMinute.offset(newCentralTime, -extent / 2)
|
||||
const domainF = d3.timeMinute.offset(newCentralTime, +extent / 2)
|
||||
|
||||
this.setState({ timerange: [domain0, domainF] }, () => {
|
||||
this.props.methods.onUpdateTimerange(this.state.timerange)
|
||||
@@ -161,7 +160,7 @@ class Timeline extends React.Component {
|
||||
* WITHOUT updating the store, or data shown.
|
||||
* Used for updates in the middle of a transition, for performance purposes
|
||||
*/
|
||||
onSoftTimeRangeUpdate(timerange) {
|
||||
onSoftTimeRangeUpdate (timerange) {
|
||||
this.setState({ timerange })
|
||||
}
|
||||
|
||||
@@ -169,26 +168,26 @@ class Timeline extends React.Component {
|
||||
* Apply zoom level to timeline
|
||||
* @param {object} zoom: zoom level from zoomLevels
|
||||
*/
|
||||
onApplyZoom(zoom) {
|
||||
onApplyZoom (zoom) {
|
||||
const extent = this.getTimeScaleExtent()
|
||||
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
|
||||
|
||||
this.setState({ timerange: [
|
||||
d3.timeMinute.offset(newCentralTime, -zoom.duration / 2),
|
||||
d3.timeMinute.offset(newCentralTime, zoom.duration / 2)
|
||||
]}, () => {
|
||||
] }, () => {
|
||||
this.props.methods.onUpdateTimerange(this.state.timerange)
|
||||
})
|
||||
}
|
||||
|
||||
toggleTransition(isTransition) {
|
||||
toggleTransition (isTransition) {
|
||||
this.setState({ transitionDuration: (isTransition) ? 300 : 0 })
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup drag behavior
|
||||
*/
|
||||
onDragStart() {
|
||||
onDragStart () {
|
||||
d3.event.sourceEvent.stopPropagation()
|
||||
this.setState({
|
||||
dragPos0: d3.event.x
|
||||
@@ -200,7 +199,7 @@ class Timeline extends React.Component {
|
||||
/*
|
||||
* Drag and update
|
||||
*/
|
||||
onDrag() {
|
||||
onDrag () {
|
||||
const drag0 = this.state.scaleX.invert(this.state.dragPos0).getTime()
|
||||
const dragNow = this.state.scaleX.invert(d3.event.x).getTime()
|
||||
const timeShift = (drag0 - dragNow) / 1000
|
||||
@@ -216,12 +215,12 @@ class Timeline extends React.Component {
|
||||
/**
|
||||
* Stop dragging and update data
|
||||
*/
|
||||
onDragEnd() {
|
||||
onDragEnd () {
|
||||
this.toggleTransition(true)
|
||||
this.props.methods.onUpdateTimerange(this.state.timerange)
|
||||
}
|
||||
|
||||
getDatetimeX(dt) {
|
||||
getDatetimeX (dt) {
|
||||
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
|
||||
* components in the <g/> div.
|
||||
*/
|
||||
styleDatetime(timestamp, category) {
|
||||
styleDatetime (timestamp, category) {
|
||||
return []
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isNarrative, app, ui } = this.props
|
||||
render () {
|
||||
const { isNarrative, app } = this.props
|
||||
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`
|
||||
classes += (app.narrative !== null) ? ' narrative-mode' : ''
|
||||
const { dims } = this.state
|
||||
@@ -257,8 +256,8 @@ class Timeline extends React.Component {
|
||||
onClick={() => { this.onClickArrow() }}
|
||||
hideInfo={isNarrative}
|
||||
/>
|
||||
<div className="timeline-content">
|
||||
<div id={this.props.ui.dom.timeline} className="timeline">
|
||||
<div className='timeline-content'>
|
||||
<div id={this.props.ui.dom.timeline} className='timeline'>
|
||||
<svg
|
||||
ref={this.svgRef}
|
||||
width={dims.width}
|
||||
@@ -319,7 +318,7 @@ class Timeline extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
isNarrative: !!state.app.narrative,
|
||||
domain: {
|
||||
@@ -334,7 +333,7 @@ function mapStateToProps(state) {
|
||||
narrative: state.app.narrative
|
||||
},
|
||||
ui: {
|
||||
dom: state.ui.dom,
|
||||
dom: state.ui.dom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,45 @@
|
||||
import React from 'react';
|
||||
/* global d3 */
|
||||
import React from 'react'
|
||||
|
||||
class TimelineAxis extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.xAxis0Ref = React.createRef();
|
||||
this.xAxis1Ref = React.createRef();
|
||||
constructor () {
|
||||
super()
|
||||
this.xAxis0Ref = React.createRef()
|
||||
this.xAxis1Ref = React.createRef()
|
||||
this.state = {
|
||||
isInitialized: false,
|
||||
isInitialized: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate () {
|
||||
if (this.props.scaleX) {
|
||||
this.x0 =
|
||||
d3.axisBottom(this.props.scaleX)
|
||||
.ticks(10)
|
||||
.tickPadding(5)
|
||||
.tickSize(this.props.dims.trackHeight)
|
||||
.tickFormat(d3.timeFormat('%d %b'));
|
||||
.tickFormat(d3.timeFormat('%d %b'))
|
||||
|
||||
this.x1 =
|
||||
d3.axisBottom(this.props.scaleX)
|
||||
.ticks(10)
|
||||
.tickPadding(this.props.dims.margin_top)
|
||||
.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) {
|
||||
d3.select(this.xAxis0Ref.current)
|
||||
.transition()
|
||||
.duration(this.props.transitionDuration)
|
||||
.call(this.x0);
|
||||
.call(this.x0)
|
||||
|
||||
d3.select(this.xAxis1Ref.current)
|
||||
.transition()
|
||||
.duration(this.props.transitionDuration)
|
||||
.call(this.x1);
|
||||
.call(this.x1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,18 +51,16 @@ class TimelineAxis extends React.Component {
|
||||
transform={`translate(0, 25)`}
|
||||
clipPath={`url(#clip)`}
|
||||
className={`axis xAxis`}
|
||||
>
|
||||
</g>
|
||||
/>
|
||||
<g
|
||||
ref={this.xAxis1Ref}
|
||||
transform={`translate(0, 105)`}
|
||||
clipPath={`url(#clip)`}
|
||||
className={`axis axisHourText`}
|
||||
>
|
||||
</g>
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TimelineAxis;
|
||||
export default TimelineAxis
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import React from 'react';
|
||||
/* global d3 */
|
||||
import React from 'react'
|
||||
|
||||
class TimelineCategories extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor () {
|
||||
super()
|
||||
this.grabRef = React.createRef()
|
||||
this.state = {
|
||||
isInitialized: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate () {
|
||||
if (!this.state.isInitialized) {
|
||||
const drag = d3.drag()
|
||||
.on('start', this.props.onDragStart)
|
||||
.on('drag', this.props.onDrag)
|
||||
.on('end', this.props.onDragEnd);
|
||||
.on('start', this.props.onDragStart)
|
||||
.on('drag', this.props.onDrag)
|
||||
.on('end', this.props.onDragEnd)
|
||||
|
||||
d3.select(this.grabRef.current)
|
||||
.call(drag);
|
||||
.call(drag)
|
||||
|
||||
this.setState({ isInitialized: true });
|
||||
this.setState({ isInitialized: true })
|
||||
}
|
||||
}
|
||||
|
||||
renderCategory(category, idx) {
|
||||
const dims = this.props.dims;
|
||||
renderCategory (category, idx) {
|
||||
const dims = this.props.dims
|
||||
return (
|
||||
<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>
|
||||
<text x={dims.margin_left - 5} dy="0.32em">{category.category}</text>
|
||||
<g class='tick' opacity='1' transform={`translate(0,${this.props.getCategoryY(category.category)})`}>
|
||||
<line x1={dims.margin_left} x2={dims.width - dims.width_controls} />
|
||||
<text x={dims.margin_left - 5} dy='0.32em'>{category.category}</text>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const dims = this.props.dims;
|
||||
const dims = this.props.dims
|
||||
|
||||
return (
|
||||
<g
|
||||
class="yAxis"
|
||||
class='yAxis'
|
||||
>
|
||||
{this.props.categories.map((cat, idx) => this.renderCategory(cat, idx))}
|
||||
<rect
|
||||
ref={this.grabRef}
|
||||
class="drag-grabber"
|
||||
class='drag-grabber'
|
||||
x={dims.margin_left}
|
||||
y="20"
|
||||
y='20'
|
||||
width={dims.width - dims.margin_left - dims.width_controls}
|
||||
height={dims.trackHeight}
|
||||
></rect>
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TimelineCategories;
|
||||
export default TimelineCategories
|
||||
|
||||
@@ -4,7 +4,7 @@ import { bindActionCreators } from 'redux'
|
||||
import * as actions from '../actions'
|
||||
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 TagListPanel from './TagListPanel.jsx'
|
||||
import ToolbarBottomActions from './ToolbarBottomActions.jsx'
|
||||
@@ -12,25 +12,25 @@ import copy from '../js/data/copy.json'
|
||||
import { trimAndEllipse } from '../js/utilities.js'
|
||||
|
||||
class Toolbar extends React.Component {
|
||||
constructor(props) {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { _selected: -1 }
|
||||
}
|
||||
|
||||
selectTab(selected) {
|
||||
selectTab (selected) {
|
||||
const _selected = (this.state._selected === selected) ? -1 : selected
|
||||
this.setState({ _selected })
|
||||
}
|
||||
|
||||
renderClosePanel() {
|
||||
renderClosePanel () {
|
||||
return (
|
||||
<div className="panel-header" onClick={() => this.selectTab(-1)}>
|
||||
<div className="caret"></div>
|
||||
<div className='panel-header' onClick={() => this.selectTab(-1)}>
|
||||
<div className='caret' />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
renderSearch() {
|
||||
renderSearch () {
|
||||
if (process.env.features.USE_SEARCH) {
|
||||
return (
|
||||
<TabPanel>
|
||||
@@ -47,19 +47,19 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
goToNarrative(narrative) {
|
||||
goToNarrative (narrative) {
|
||||
this.selectTab(-1) // set all unselected within this component
|
||||
this.props.methods.onSelectNarrative(narrative)
|
||||
}
|
||||
|
||||
renderToolbarNarrativePanel() {
|
||||
renderToolbarNarrativePanel () {
|
||||
return (
|
||||
<TabPanel>
|
||||
<h2>{copy[this.props.language].toolbar.narrative_panel_title}</h2>
|
||||
<p>{copy[this.props.language].toolbar.narrative_summary}</p>
|
||||
{this.props.narratives.map((narr) => {
|
||||
return (
|
||||
<div className="panel-action action">
|
||||
<div className='panel-action action'>
|
||||
<button style={{ backgroundColor: '#000' }} onClick={() => { this.goToNarrative(narr) }}>
|
||||
<p>{narr.label}</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 &&
|
||||
this.props.tags.children) {
|
||||
return (
|
||||
@@ -91,19 +91,20 @@ class Toolbar extends React.Component {
|
||||
return ''
|
||||
}
|
||||
|
||||
renderToolbarTab(_selected, label, icon_key) {
|
||||
renderToolbarTab (_selected, label, iconKey) {
|
||||
console.log(label)
|
||||
const isActive = (this.state._selected === _selected)
|
||||
let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab'
|
||||
|
||||
return (
|
||||
<div className={classes} onClick={() => { this.selectTab(_selected) }}>
|
||||
<i className="material-icons">{icon_key}</i>
|
||||
<div className="tab-caption">{label}</div>
|
||||
<i className='material-icons'>{iconKey}</i>
|
||||
<div className='tab-caption'>{label}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderToolbarPanels() {
|
||||
renderToolbarPanels () {
|
||||
let classes = (this.state._selected >= 0) ? 'toolbar-panels' : 'toolbar-panels folded'
|
||||
return (
|
||||
<div className={classes}>
|
||||
@@ -116,7 +117,7 @@ class Toolbar extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderToolbarNavs() {
|
||||
renderToolbarNavs () {
|
||||
if (this.props.narratives) {
|
||||
return this.props.narratives.map((nar, idx) => {
|
||||
const isActive = (idx === this.state._selected)
|
||||
@@ -125,7 +126,7 @@ class Toolbar extends React.Component {
|
||||
|
||||
return (
|
||||
<div className={classes} onClick={() => { this.selectTab(idx) }}>
|
||||
<div className="tab-caption">{nar.label}</div>
|
||||
<div className='tab-caption'>{nar.label}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -133,36 +134,35 @@ class Toolbar extends React.Component {
|
||||
return ''
|
||||
}
|
||||
|
||||
renderToolbarTabs() {
|
||||
renderToolbarTabs () {
|
||||
let title = copy[this.props.language].toolbar.title
|
||||
if (process.env.title) title = process.env.title
|
||||
const narratives_label = copy[this.props.language].toolbar.narratives_label
|
||||
const tags_label = copy[this.props.language].toolbar.tags_label
|
||||
const narrativesLabel = copy[this.props.language].toolbar.narratives_label
|
||||
const tagsLabel = copy[this.props.language].toolbar.tags_label
|
||||
const isTags = this.props.tags && this.props.tags.children
|
||||
|
||||
return (
|
||||
<div className="toolbar">
|
||||
<div className="toolbar-header"><p>{title}</p></div>
|
||||
<div className="toolbar-tabs">
|
||||
{/*this.renderToolbarTab(0, 'search')*/}
|
||||
{this.renderToolbarTab(0, narratives_label, 'timeline')}
|
||||
{(isTags) ? this.renderToolbarTab(1, tags_label, 'style') : ''}
|
||||
<div className='toolbar'>
|
||||
<div className='toolbar-header'><p>{title}</p></div>
|
||||
<div className='toolbar-tabs'>
|
||||
{this.renderToolbarTab(0, narrativesLabel, 'timeline')}
|
||||
{(isTags) ? this.renderToolbarTab(1, tagsLabel, 'style') : ''}
|
||||
</div>
|
||||
<ToolbarBottomActions
|
||||
sites={{
|
||||
enabled: this.props.sitesShowing,
|
||||
toggle: this.props.actions.toggleSites,
|
||||
toggle: this.props.actions.toggleSites
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { isNarrative } = this.props
|
||||
|
||||
return (
|
||||
<div id="toolbar-wrapper" className={`toolbar-wrapper ${(isNarrative) ? 'narrative-mode' : ''}`}>
|
||||
<div id='toolbar-wrapper' className={`toolbar-wrapper ${(isNarrative) ? 'narrative-mode' : ''}`}>
|
||||
{this.renderToolbarTabs()}
|
||||
{this.renderToolbarPanels()}
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
tags: selectors.getTagTree(state),
|
||||
categories: selectors.getCategories(state),
|
||||
@@ -185,7 +185,7 @@ function mapStateToProps(state) {
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch)
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
import SitesIcon from './presentational/Icons/SitesIcon.js';
|
||||
// import RefreshIcon from './presentational/Icons/RefreshIcon.js';
|
||||
// import CoeventIcon from './presentational/Icons/CoeventIcon.js';
|
||||
// import RouteIcon from './presentational/Icons/RouteIcon.js';
|
||||
import SitesIcon from './presentational/Icons/Sites.js'
|
||||
// import RefreshIcon from './presentational/Icons/RefreshIcon.js'
|
||||
// import CoeventIcon from './presentational/Icons/CoeventIcon.js'
|
||||
// import RouteIcon from './presentational/Icons/RouteIcon.js'
|
||||
|
||||
function ToolbarBottomActions (props) {
|
||||
function renderMapActions() {
|
||||
function renderMapActions () {
|
||||
return (
|
||||
<div className="bottom-action-block">
|
||||
<div className='bottom-action-block'>
|
||||
{process.env.features.USE_SITES ? <SitesIcon
|
||||
isActive={props.sites.enabled}
|
||||
onClickHandler={props.sites.toggle}
|
||||
/> : null}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bottom-actions">
|
||||
<div className='bottom-actions'>
|
||||
{renderMapActions()}
|
||||
<div className="bottom-action-block">
|
||||
{/* <button className="action-button tiny default" onClick={() => { toggleLanguage()}}> */}
|
||||
<div className='bottom-action-block'>
|
||||
{/* <button className='action-button tiny default' onClick={() => { toggleLanguage()}}> */}
|
||||
{/* {(props.language === 'es-MX') ? 'ES' : 'EN' } */}
|
||||
{/* </button> */}
|
||||
{/* <button className="action-button info tiny default" onClick={() => { this.toggleInfoPopup()}}> */}
|
||||
{/* <button className='action-button info tiny default' onClick={() => { this.toggleInfoPopup()}}> */}
|
||||
{/* i */}
|
||||
{/* </button> */}
|
||||
{/* <button className="action-button tiny" onClick={() => this.resetAllFilters()}> */}
|
||||
{/* <button className='action-button tiny' onClick={() => this.resetAllFilters()}> */}
|
||||
{/* <RefreshIcon /> */}
|
||||
{/* </button> */}
|
||||
</div>
|
||||
@@ -35,4 +35,4 @@ function ToolbarBottomActions (props) {
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolbarBottomActions;
|
||||
export default ToolbarBottomActions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
const RefreshIcon = () => {
|
||||
export default ({ isActive, isDisabled, onClickHandler }) => {
|
||||
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'>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
export default RefreshIcon
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
const MapDefsMarkers = ({}) => (
|
||||
const MapDefsMarkers = () => (
|
||||
<defs>
|
||||
<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>
|
||||
<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' }} />
|
||||
</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 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>
|
||||
);
|
||||
)
|
||||
|
||||
export default MapDefsMarkers;
|
||||
export default MapDefsMarkers
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Portal } from 'react-portal';
|
||||
import React from 'react'
|
||||
import { Portal } from 'react-portal'
|
||||
|
||||
function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, narrative, onSelect, svg, locations }){
|
||||
function getLocationEventsDistribution(location) {
|
||||
const eventCount = {};
|
||||
const categories = categories;
|
||||
function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, narrative, onSelect, svg, locations }) {
|
||||
// function getLocationEventsDistribution (location) {
|
||||
// const eventCount = {}
|
||||
//
|
||||
// categories.forEach(cat => {
|
||||
// eventCount[cat.category] = []
|
||||
// })
|
||||
//
|
||||
// location.events.forEach((event) => {
|
||||
// ;
|
||||
// eventCount[event.category].push(event)
|
||||
// })
|
||||
//
|
||||
// return eventCount
|
||||
// }
|
||||
|
||||
categories.forEach(cat => {
|
||||
eventCount[cat.category] = [];
|
||||
});
|
||||
|
||||
location.events.forEach((event) => {;
|
||||
eventCount[event.category].push(event);
|
||||
});
|
||||
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
function renderLocation(location) {
|
||||
function renderLocation (location) {
|
||||
/**
|
||||
{
|
||||
events: [...],
|
||||
@@ -26,7 +26,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
longitude: '32.2'
|
||||
}
|
||||
*/
|
||||
const { x, y } = projectPoint([location.latitude, location.longitude]);
|
||||
const { x, y } = projectPoint([location.latitude, location.longitude])
|
||||
// const eventsByCategory = getLocationEventsDistribution(location);
|
||||
|
||||
const locCategory = location.events.length > 0 ? location.events[0].category : 'default'
|
||||
@@ -37,7 +37,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
const styles = ({
|
||||
fill: getCategoryColor(locCategory),
|
||||
fillOpacity: 1,
|
||||
...customStyles[0]
|
||||
...extraStyles
|
||||
})
|
||||
|
||||
// in narrative mode, only render events in narrative
|
||||
@@ -53,16 +53,15 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
|
||||
return (
|
||||
<g
|
||||
className="location"
|
||||
className='location'
|
||||
transform={`translate(${x}, ${y})`}
|
||||
onClick={() => onSelect(location.events)}
|
||||
>
|
||||
<circle
|
||||
className="location-event-marker"
|
||||
className='location-event-marker'
|
||||
r={7}
|
||||
style={styles}
|
||||
>
|
||||
</circle>
|
||||
/>
|
||||
{extraRender ? extraRender() : null}
|
||||
</g>
|
||||
)
|
||||
@@ -72,7 +71,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
<Portal node={svg}>
|
||||
{locations.map(renderLocation)}
|
||||
</Portal>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default MapEvents;
|
||||
export default MapEvents
|
||||
|
||||
@@ -2,40 +2,39 @@ import React from 'react'
|
||||
import { Portal } from 'react-portal'
|
||||
|
||||
function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) {
|
||||
function getNarrativeStyle(narrativeId) {
|
||||
function getNarrativeStyle (narrativeId) {
|
||||
const styleName = (narrativeId && narrativeId in styles)
|
||||
? narrativeId
|
||||
: 'default'
|
||||
return styles[styleName]
|
||||
}
|
||||
|
||||
function getStepStyle(name) {
|
||||
function getStepStyle (name) {
|
||||
if (name === 'None') return null
|
||||
return styles.stepStyles[name]
|
||||
}
|
||||
|
||||
function hasNoLocation(step) {
|
||||
function hasNoLocation (step) {
|
||||
return (step.latitude === '' || step.longitude === '')
|
||||
}
|
||||
|
||||
function renderNarrativeStep(idx, n) {
|
||||
function renderNarrativeStep (idx, n) {
|
||||
const step = n.steps[idx]
|
||||
const step2 = n.steps[idx + 1]
|
||||
|
||||
// don't draw if one of the steps has no location
|
||||
if (hasNoLocation(step) || hasNoLocation(step2))
|
||||
return null
|
||||
if (hasNoLocation(step) || hasNoLocation(step2)) { return null }
|
||||
|
||||
// 0 if not in narrative mode, 1 if active narrative, 0.1 if inactive
|
||||
let styles = {
|
||||
strokeOpacity: (n === null) ? 0
|
||||
: (step && (n.id === narrative.id)) ? 1 : 0.1,
|
||||
strokeOpacity: (n === null) ? 0
|
||||
: (step && (n.id === narrative.id)) ? 1 : 0.1,
|
||||
strokeWidth: 0,
|
||||
strokeDasharray: 'none',
|
||||
stroke: 'none'
|
||||
}
|
||||
|
||||
const p1 = projectPoint([step.latitude, step.longitude])
|
||||
const p1 = projectPoint([step.latitude, step.longitude])
|
||||
const p2 = projectPoint([step2.latitude, step2.longitude])
|
||||
|
||||
if (step) {
|
||||
@@ -55,40 +54,37 @@ function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives,
|
||||
...styles,
|
||||
...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
|
||||
return (
|
||||
<line
|
||||
className="narrative-step"
|
||||
className='narrative-step'
|
||||
x1={p1.x}
|
||||
x2={p2.x}
|
||||
y1={p1.y}
|
||||
y2={p2.y}
|
||||
markerStart="none"
|
||||
onClick={() => onSelectNarrative(n)}
|
||||
markerStart='none'
|
||||
onClick={n => onSelectNarrative(n)}
|
||||
style={{
|
||||
strokeWidth,
|
||||
strokeDasharray,
|
||||
strokeOpacity,
|
||||
stroke,
|
||||
stroke
|
||||
}}
|
||||
>
|
||||
</line>
|
||||
/>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
function renderNarrative(n) {
|
||||
function renderNarrative (n) {
|
||||
const steps = n.steps.slice(0, n.steps.length - 1)
|
||||
|
||||
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))}
|
||||
</g>
|
||||
)
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Portal } from 'react-portal';
|
||||
import React from 'react'
|
||||
import { Portal } from 'react-portal'
|
||||
|
||||
class MapSelectedEvents extends React.Component {
|
||||
renderMarker (event) {
|
||||
const { x, y } = this.props.projectPoint([event.latitude, event.longitude]);
|
||||
const { x, y } = this.props.projectPoint([event.latitude, event.longitude])
|
||||
return (
|
||||
<g
|
||||
className="location-marker"
|
||||
className='location-marker'
|
||||
transform={`translate(${x - 32}, ${y})`}
|
||||
>
|
||||
<path
|
||||
className="leaflet-interactive"
|
||||
stroke="#ffffff"
|
||||
stroke-opacity="1"
|
||||
stroke-width="3"
|
||||
stroke-linecap=""
|
||||
stroke-linejoin="round"
|
||||
stroke-dasharray="5,2"
|
||||
fill="none"
|
||||
d="M0,0a32,32 0 1,0 64,0 a32,32 0 1,0 -64,0 "
|
||||
>
|
||||
</path>
|
||||
className='leaflet-interactive'
|
||||
stroke='#ffffff'
|
||||
stroke-opacity='1'
|
||||
stroke-width='3'
|
||||
stroke-linecap=''
|
||||
stroke-linejoin='round'
|
||||
stroke-dasharray='5,2'
|
||||
fill='none'
|
||||
d='M0,0a32,32 0 1,0 64,0 a32,32 0 1,0 -64,0 '
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
return (
|
||||
<Portal node={this.props.svg}>
|
||||
{this.props.selected.map(s => this.renderMarker(s))}
|
||||
@@ -33,4 +32,4 @@ class MapSelectedEvents extends React.Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
export default MapSelectedEvents;
|
||||
export default MapSelectedEvents
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Portal } from 'react-portal'
|
||||
|
||||
function MapShapes({ svg, shapes, projectPoint, styles }) {
|
||||
function renderShape(shape) {
|
||||
function MapShapes ({ svg, shapes, projectPoint, styles }) {
|
||||
function renderShape (shape) {
|
||||
const lineCoords = []
|
||||
const points = shape.points
|
||||
.map(projectPoint)
|
||||
|
||||
points.forEach((p1, idx) => {
|
||||
if (idx < shape.points.length - 1) {
|
||||
const p2 = points[idx+1]
|
||||
const p2 = points[idx + 1]
|
||||
lineCoords.push({
|
||||
x1: p1.x,
|
||||
y1: p1.y,
|
||||
@@ -27,11 +27,10 @@ function MapShapes({ svg, shapes, projectPoint, styles }) {
|
||||
return (
|
||||
<line
|
||||
id={`${shape.name}_style`}
|
||||
markerStart="none"
|
||||
markerStart='none'
|
||||
{...coords}
|
||||
style={shapeStyles}
|
||||
>
|
||||
</line>
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -40,12 +39,11 @@ function MapShapes({ svg, shapes, projectPoint, styles }) {
|
||||
|
||||
return (
|
||||
<Portal node={svg}>
|
||||
<g id={`shapes-layer`} className="narrative">
|
||||
<g id={`shapes-layer`} className='narrative'>
|
||||
{shapes.map(renderShape)}
|
||||
</g>
|
||||
</Portal>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default MapShapes
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import React from 'react';
|
||||
import React from 'react'
|
||||
|
||||
function MapSites({ sites, projectPoint }) {
|
||||
function renderSite(site) {
|
||||
const { x, y } = projectPoint([site.latitude, site.longitude]);
|
||||
function MapSites ({ sites, projectPoint }) {
|
||||
function renderSite (site) {
|
||||
const { x, y } = projectPoint([site.latitude, site.longitude])
|
||||
|
||||
return (<div
|
||||
className="leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top"
|
||||
style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)`}}>
|
||||
className='leaflet-tooltip site-label leaflet-zoom-animated leaflet-tooltip-top'
|
||||
style={{ opacity: 1, transform: `translate3d(calc(${x}px - 50%), ${y - 25}px, 0px)` }}>
|
||||
{site.site}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (!sites || !sites.length) return null;
|
||||
if (!sites || !sites.length) return null
|
||||
|
||||
return (
|
||||
<div className="sites-layer">
|
||||
<div className='sites-layer'>
|
||||
{sites.map(renderSite)}
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default MapSites;
|
||||
export default MapSites
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store/index.js';
|
||||
import App from './components/App.jsx';
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import store from './store/index.js'
|
||||
import App from './components/App.jsx'
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('explore-app')
|
||||
);
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user