mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 05:18:34 +03:00
Merge remote-tracking branch 'origin/develop' into add-linting
This commit is contained in:
BIN
src/components/presentational/.DS_Store
vendored
Normal file
BIN
src/components/presentational/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -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;
|
||||
30
src/components/presentational/Card/Location.js
Normal file
30
src/components/presentational/Card/Location.js
Normal 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;
|
||||
@@ -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>← <CardNarrativeLink {...props} event={props.next} /></p>
|
||||
<div className="card-cell">
|
||||
<p>← <CardNarrativeLink {...props} event={props.next}/></p>
|
||||
<p>→ <CardNarrativeLink {...props} event={props.prev} /></p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default CardNarrative
|
||||
export default CardNarrative;
|
||||
@@ -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
|
||||
19
src/components/presentational/Card/Summary.js
Normal file
19
src/components/presentational/Card/Summary.js
Normal 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;
|
||||
37
src/components/presentational/Card/Tags.js
Normal file
37
src/components/presentational/Card/Tags.js
Normal 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;
|
||||
34
src/components/presentational/Card/Timestamp.js
Normal file
34
src/components/presentational/Card/Timestamp.js
Normal 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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
14
src/components/presentational/Map/DefsMarkers.jsx
Normal file
14
src/components/presentational/Map/DefsMarkers.jsx
Normal 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;
|
||||
78
src/components/presentational/Map/Events.jsx
Normal file
78
src/components/presentational/Map/Events.jsx
Normal 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;
|
||||
106
src/components/presentational/Map/Narratives.jsx
Normal file
106
src/components/presentational/Map/Narratives.jsx
Normal 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
|
||||
36
src/components/presentational/Map/SelectedEvents.jsx
Normal file
36
src/components/presentational/Map/SelectedEvents.jsx
Normal 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;
|
||||
51
src/components/presentational/Map/Shapes.jsx
Normal file
51
src/components/presentational/Map/Shapes.jsx
Normal 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
|
||||
25
src/components/presentational/Map/Sites.jsx
Normal file
25
src/components/presentational/Map/Sites.jsx
Normal 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;
|
||||
@@ -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
|
||||
@@ -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 --'
|
||||
/>
|
||||
@@ -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;
|
||||
26
src/components/presentational/Timeline/Handles.js
Normal file
26
src/components/presentational/Timeline/Handles.js
Normal 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;
|
||||
44
src/components/presentational/Timeline/Labels.js
Normal file
44
src/components/presentational/Timeline/Labels.js
Normal 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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user