Merge remote-tracking branch 'origin/develop' into add-linting

This commit is contained in:
Lachlan Kermode
2019-01-22 15:34:07 +00:00
53 changed files with 746 additions and 617 deletions

BIN
src/components/presentational/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,15 +1,15 @@
import React from 'react'
import React from 'react';
import { capitalizeFirstLetter } from '../../js/utilities.js'
import { capitalizeFirstLetter } from '../../../js/utilities.js';
const CardCategory = ({ categoryTitle, categoryLabel, color }) => (
<div className='card-row card-cell category'>
<div className="card-row card-cell category">
<h4>{categoryTitle}</h4>
<p>
{capitalizeFirstLetter(categoryLabel)}
<span className='color-category' style={{ background: color }} />
<span className='color-category' style={{ background: color }}/>
</p>
</div>
)
);
export default CardCategory
export default CardCategory;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import copy from '../../../js/data/copy.json';
import { isNotNullNorUndefined } from '../../../js/utilities';
const CardLocation = ({ language, location }) => {
if (isNotNullNorUndefined(location)) {
return (
<div className="card-cell location">
<p>
<i className="material-icons left">location_on</i>
{location}
</p>
</div>
);
} else {
const unknown = copy[language].cardstack.unknown_location;
return (
<div className="card-cell location">
<p>
<i className="material-icons left">location_on</i>
{unknown}
</p>
</div>
);
}
}
export default CardLocation;

View File

@@ -1,15 +1,15 @@
import React from 'react'
import React from 'react';
import CardNarrativeLink from './CardNarrativeLink'
import CardNarrativeLink from './NarrativeLink';
const CardNarrative = (props) => (
<div className='card-row'>
<div className="card-row">
<h4>Connected events</h4>
<div className='card-cell'>
<p>&larr; <CardNarrativeLink {...props} event={props.next} /></p>
<div className="card-cell">
<p>&larr; <CardNarrativeLink {...props} event={props.next}/></p>
<p>&rarr; <CardNarrativeLink {...props} event={props.prev} /></p>
</div>
</div>
)
);
export default CardNarrative
export default CardNarrative;

View File

@@ -1,11 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import Spinner from './Spinner'
import Img from 'react-image'
import Spinner from '../Spinner'
import copy from '../../../js/data/copy.json'
const CardSource = ({ source, isLoading, onClickHandler }) => {
function renderIconText (type) {
switch (type) {
function renderIconText(type) {
switch(type) {
case 'Eyewitness Testimony':
return 'visibility'
case 'Government Data':
@@ -27,7 +29,7 @@ const CardSource = ({ source, isLoading, onClickHandler }) => {
if (!source) {
return (
<div className='card-source'>
<div className="card-source">
<div>Error: this source was not found</div>
</div>
)
@@ -43,30 +45,30 @@ const CardSource = ({ source, isLoading, onClickHandler }) => {
}
const fallbackIcon = (
<i className='material-icons source-icon'>
<i className="material-icons source-icon">
{renderIconText(source.type)}
</i>
)
return (
<div className='card-source'>
<div className="card-source">
{isLoading
? <Spinner />
: (
<div className='source-row' onClick={() => onClickHandler(source)}>
{thumbnail ? (
<Img
className='source-icon'
src={thumbnail}
loader={<Spinner small />}
unloader={fallbackIcon}
width={30}
height={30}
/>
) : fallbackIcon}
<p>{source.id}</p>
</div>
)}
? <Spinner/>
: (
<div className="source-row" onClick={() => onClickHandler(source)}>
{!!thumbnail ? (
<Img
className="source-icon"
src={thumbnail}
loader={<Spinner small />}
unloader={fallbackIcon}
width={30}
height={30}
/>
) : fallbackIcon}
<p>{source.id}</p>
</div>
)}
</div>
)
}
@@ -77,7 +79,7 @@ CardSource.propTypes = {
type: PropTypes.string
}),
isLoading: PropTypes.bool,
onClickHandler: PropTypes.func.isRequired
onClickHandler: PropTypes.func.isRequired,
}
export default CardSource

View File

@@ -0,0 +1,19 @@
import React from 'react';
import copy from '../../../js/data/copy.json';
const CardSummary = ({ language, description, isHighlighted }) => {
const summary = copy[language].cardstack.description;
return (
<div className="card-row summary">
<div className="card-cell">
<h4>{summary}</h4>
<p>{description}</p>
</div>
</div>
);
}
export default CardSummary;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import copy from '../../../js/data/copy.json';
const CardTags = ({ tags, language }) => {
const tags_lang = copy[language].cardstack.tags;
const no_tags_lang = copy[language].cardstack.notags;
if (tags.length > 0) {
return (
<div className="card-row card-cell tags">
<h4>{tags_lang}:</h4>
<p>
{tags.map((tag, idx) => {
return (
<span className="tag">
<small>{tag.name}</small>
{(idx < tags.length - 1)
? ','
: ''}
</span>
);
})}
</p>
</div>
);
}
return (
<div className="card-row card-cell tags">
<h4>{tags_lang}</h4>
<p><small>{no_tags_lang}</small></p>
</div>
);
}
export default CardTags;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import copy from '../../../js/data/copy.json';
import { isNotNullNorUndefined } from '../../../js/utilities';
const CardTimestamp = ({ makeTimelabel, language, timestamp }) => {
const daytime_lang = copy[language].cardstack.timestamp;
const estimated_lang = copy[language].cardstack.estimated;
const unknown_lang = copy[language].cardstack.unknown_time;
if (isNotNullNorUndefined(timestamp)) {
const timelabel = makeTimelabel(timestamp);
return (
<div className="card-cell timestamp">
<p>
<i className="material-icons left">today</i>
{timelabel}
</p>
</div>
);
} else {
return (
<div className="card-cell timestamp">
<p>
<i className="material-icons left">today</i>
{unknown_lang}
</p>
</div>
);
}
}
export default CardTimestamp;

View File

@@ -1,29 +0,0 @@
import React from 'react'
import copy from '../../js/data/copy.json'
import { isNotNullNorUndefined } from '../../js/utilities'
const CardLocation = ({ language, location }) => {
if (isNotNullNorUndefined(location)) {
return (
<div className='card-cell location'>
<p>
<i className='material-icons left'>location_on</i>
{location}
</p>
</div>
)
} else {
const unknown = copy[language].cardstack.unknown_location
return (
<div className='card-cell location'>
<p>
<i className='material-icons left'>location_on</i>
{unknown}
</p>
</div>
)
}
}
export default CardLocation

View File

@@ -1,18 +0,0 @@
import React from 'react'
import copy from '../../js/data/copy.json'
const CardSummary = ({ language, description, isHighlighted }) => {
const summary = copy[language].cardstack.description
return (
<div className='card-row summary'>
<div className='card-cell'>
<h4>{summary}</h4>
<p>{description}</p>
</div>
</div>
)
}
export default CardSummary

View File

@@ -1,36 +0,0 @@
import React from 'react'
import copy from '../../js/data/copy.json'
const CardTags = ({ tags, language }) => {
const tagsLang = copy[language].cardstack.tags
const noTagsLang = copy[language].cardstack.notags
if (tags.length > 0) {
return (
<div className='card-row card-cell tags'>
<h4>{tagsLang}:</h4>
<p>
{tags.map((tag, idx) => {
return (
<span className='tag'>
<small>{tag.name}</small>
{(idx < tags.length - 1)
? ','
: ''}
</span>
)
})}
</p>
</div>
)
}
return (
<div className='card-row card-cell tags'>
<h4>{tagsLang}</h4>
<p><small>{noTagsLang}</small></p>
</div>
)
}
export default CardTags

View File

@@ -1,31 +0,0 @@
import React from 'react'
import copy from '../../js/data/copy.json'
import { isNotNullNorUndefined } from '../../js/utilities'
const CardTimestamp = ({ makeTimelabel, language, timestamp }) => {
const unknownLang = copy[language].cardstack.unknown_time
if (isNotNullNorUndefined(timestamp)) {
const timelabel = makeTimelabel(timestamp)
return (
<div className='card-cell timestamp'>
<p>
<i className='material-icons left'>today</i>
{timelabel}
</p>
</div>
)
} else {
return (
<div className='card-cell timestamp'>
<p>
<i className='material-icons left'>today</i>
{unknownLang}
</p>
</div>
)
}
}
export default CardTimestamp

View File

@@ -0,0 +1,14 @@
import React from 'react';
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>
<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;

View File

@@ -0,0 +1,78 @@
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;
categories.forEach(cat => {
eventCount[cat.category] = [];
});
location.events.forEach((event) => {;
eventCount[event.category].push(event);
});
return eventCount;
}
function renderLocation(location) {
/**
{
events: [...],
label: 'Location name',
latitude: '47.7',
longitude: '32.2'
}
*/
const { x, y } = projectPoint([location.latitude, location.longitude]);
// const eventsByCategory = getLocationEventsDistribution(location);
const locCategory = location.events.length > 0 ? location.events[0].category : 'default'
const customStyles = styleLocation ? styleLocation(location) : null
const extraStyles = customStyles[0]
const extraRender = customStyles[1]
const styles = ({
fill: getCategoryColor(locCategory),
fillOpacity: 1,
...customStyles[0]
})
// in narrative mode, only render events in narrative
if (narrative) {
const { steps } = narrative
const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id)
const eventsInNarrative = location.events.filter(onlyIfInNarrative)
if (eventsInNarrative.length <= 0) {
return null
}
}
return (
<g
className="location"
transform={`translate(${x}, ${y})`}
onClick={() => onSelect(location.events)}
>
<circle
className="location-event-marker"
r={7}
style={styles}
>
</circle>
{extraRender ? extraRender() : null}
</g>
)
}
return (
<Portal node={svg}>
{locations.map(renderLocation)}
</Portal>
);
}
export default MapEvents;

View File

@@ -0,0 +1,106 @@
import React from 'react'
import { Portal } from 'react-portal'
function MapNarratives ({ styles, onSelectNarrative, svg, narrative, narratives, projectPoint }) {
function getNarrativeStyle(narrativeId) {
const styleName = (narrativeId && narrativeId in styles)
? narrativeId
: 'default'
return styles[styleName]
}
function getStepStyle(name) {
if (name === 'None') return null
return styles.stepStyles[name]
}
function hasNoLocation(step) {
return (step.latitude === '' || step.longitude === '')
}
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
// 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,
strokeWidth: 0,
strokeDasharray: 'none',
stroke: 'none'
}
const p1 = projectPoint([step.latitude, step.longitude])
const p2 = projectPoint([step2.latitude, step2.longitude])
if (step) {
if (process.env.features.NARRATIVE_STEP_STYLES) {
const _idx = step.narratives.indexOf(n.id)
const stepStyle = step.narrative___stepStyles[_idx]
return _renderNarrativeStep(
p1,
p2,
{ ...styles, ...getStepStyle(stepStyle) }
)
// otherwise steps are styled per narrative
} else {
styles = {
...styles,
...getNarrativeStyle(n.id)
}
return _renderNarrativeStep(p1,p2,styles)
}
}
}
function _renderNarrativeStep(p1, p2, styles) {
const { stroke, strokeWidth, strokeDasharray, strokeOpacity } = styles
return (
<line
className="narrative-step"
x1={p1.x}
x2={p2.x}
y1={p1.y}
y2={p2.y}
markerStart="none"
onClick={() => onSelectNarrative(n)}
style={{
strokeWidth,
strokeDasharray,
strokeOpacity,
stroke,
}}
>
</line>
)
}
function renderNarrative(n) {
const steps = n.steps.slice(0, n.steps.length - 1)
return (
<g id={`narrative-${n.id.replace(/ /g,"_")}`} className="narrative">
{steps.map((s, idx) => renderNarrativeStep(idx, n))}
</g>
)
}
if (narrative === null) return (<div />)
return (
<Portal node={svg}>
{narratives.map(n => renderNarrative(n))}
</Portal>
)
}
export default MapNarratives

View File

@@ -0,0 +1,36 @@
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]);
return (
<g
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>
</g>
);
}
render() {
return (
<Portal node={this.props.svg}>
{this.props.selected.map(s => this.renderMarker(s))}
</Portal>
)
}
}
export default MapSelectedEvents;

View File

@@ -0,0 +1,51 @@
import React from 'react'
import { Portal } from 'react-portal'
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]
lineCoords.push({
x1: p1.x,
y1: p1.y,
x2: p2.x,
y2: p2.y
})
}
})
return lineCoords.map(coords => {
const shapeStyles = (shape.name in styles)
? styles[shape.name]
: styles.default
return (
<line
id={`${shape.name}_style`}
markerStart="none"
{...coords}
style={shapeStyles}
>
</line>
)
})
}
if (!shapes || !shapes.length) return null
return (
<Portal node={svg}>
<g id={`shapes-layer`} className="narrative">
{shapes.map(renderShape)}
</g>
</Portal>
)
}
export default MapShapes

View File

@@ -0,0 +1,25 @@
import React from 'react';
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)`}}>
{site.site}
</div>
);
}
if (!sites || !sites.length) return null;
return (
<div className="sites-layer">
{sites.map(renderSite)}
</div>
)
}
export default MapSites;

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { connect } from 'react-redux'
import { selectActiveNarrative } from '../../selectors'
import { selectActiveNarrative } from '../../../selectors'
function NarrativeCard ({ narrative }) {
// no display if no narrative

View File

@@ -1,7 +1,7 @@
import React from 'react'
import NarrativeCard from './NarrativeCard'
import NarrativeAdjust from './NarrativeAdjust'
import NarrativeClose from './NarrativeClose'
import Card from './Card'
import Adjust from './Adjust'
import Close from './Close'
export default ({ narrative, methods }) => {
if (!narrative) return null
@@ -12,18 +12,18 @@ export default ({ narrative, methods }) => {
return (
<React.Fragment>
<NarrativeCard narrative={narrative} />
<NarrativeAdjust
<Card narrative={narrative} />
<Adjust
isDisabled={!prevExists}
direction='left'
onClickHandler={methods.onPrev}
/>
<NarrativeAdjust
<Adjust
isDisabled={!nextExists}
direction='right'
onClickHandler={methods.onNext}
/>
<NarrativeClose
<Close
onClickHandler={() => methods.onSelectNarrative(null)}
closeMsg='-- exit from narrative --'
/>

View File

@@ -1,14 +1,15 @@
import React from 'react'
import React from 'react';
const TimelineClip = ({ dims }) => (
<clipPath id='clip'>
<clipPath id="clip">
<rect
x='120'
y='0'
x={dims.margin_left}
y="0"
width={dims.width - dims.margin_left - dims.width_controls}
height={dims.height - 25}
/>
>
</rect>
</clipPath>
)
);
export default TimelineClip
export default TimelineClip;

View File

@@ -0,0 +1,26 @@
import React from 'react';
const TimelineHandles = ({ dims, onMoveTime }) => {
return (
<g className="time-controls-inline">
<g
transform={`translate(${dims.margin_left - 20}, 120)`}
onClick={() => onMoveTime('backwards')}
>
<circle r="15"></circle>
<path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z" transform="rotate(270)"></path>
</g>
<g
transform={`translate(${dims.width - dims.width_controls + 20}, 120)`}
onClick={() => onMoveTime('forward')}
>
<circle r="15"></circle>
<path d="M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z" transform="rotate(90)"></path>
</g>
</g>
)
}
export default TimelineHandles;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { formatterWithYear } from '../../../js/utilities.js';
const TimelineLabels = ({ dims, timelabels }) => {
return (
<g>
<line
class="axisBoundaries"
x1={dims.margin_left}
x2={dims.margin_left}
y1="10"
y2="20"
>
</line>
<line
class="axisBoundaries"
x1={dims.width - dims.width_controls}
x2={dims.width - dims.width_controls}
y1="10"
y2="20"
>
</line>
<text
class="timeLabel0 timeLabel"
x="5"
y="15"
>
{formatterWithYear(timelabels[0])}
</text>
<text
class="timelabelF timeLabel"
x={dims.width - dims.width_controls - 5}
y="15"
style={{ textAnchor: 'end' }}
>
{formatterWithYear(timelabels[1])}
</text>
</g>
)
}
export default TimelineLabels;

View File

@@ -1,24 +0,0 @@
import React from 'react'
const TimelineHandles = ({ dims, onMoveTime }) => {
return (
<g className='time-controls-inline'>
<g
transform={`translate(${dims.margin_left + 20}, 62)`}
onClick={() => onMoveTime('backwards')}
>
<circle r='15' />
<path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(270)' />
</g>
<g
transform={`translate(${dims.width - dims.width_controls - 20}, 62)`}
onClick={() => onMoveTime('forward')}
>
<circle r='15' />
<path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(90)' />
</g>
</g>
)
}
export default TimelineHandles

View File

@@ -1,39 +0,0 @@
import React from 'react'
const TimelineLabels = ({ dims, timelabels }) => {
return (
<g>
<line
class='axisBoundaries'
x1={dims.margin_left}
x2={dims.margin_left}
y1='10'
y2='20'
/>
<line
class='axisBoundaries'
x1={dims.width - dims.width_controls}
x2={dims.width - dims.width_controls}
y1='10'
y2='20'
/>
{/* <text */}
{/* class="timeLabel0 timeLabel" */}
{/* x="5" */}
{/* y="15" */}
{/* > */}
{/* {formatterWithYear(timelabels[0])} */}
{/* </text> */}
{/* <text */}
{/* class="timelabelF timeLabel" */}
{/* x={dims.width - dims.width_controls - 5} */}
{/* y="15" */}
{/* style={{ textAnchor: 'end' }} */}
{/* > */}
{/* {formatterWithYear(timelabels[1])} */}
{/* </text> */}
</g>
)
}
export default TimelineLabels