significantly refactor presentational components

This commit is contained in:
Lachlan Kermode
2019-01-18 11:51:00 +00:00
parent f561064e6c
commit e7cac13fb5
34 changed files with 103 additions and 150 deletions

View File

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

View File

@@ -0,0 +1,28 @@
import React from 'react'
export default ({
category,
events,
x,
y,
onSelect,
styleProps,
extraRender
}) => (
<g
className='datetime'
transform={`translate(${x}, ${y})`}
onClick={() => onSelect(events)}
>
<circle
className="event"
cx={0}
cy={0}
style={styleProps}
r={5}
>
</circle>
{ extraRender ? extraRender() : null }
</g>
)

View File

@@ -0,0 +1,90 @@
import React from 'react';
import DatetimeDot from './DatetimeDot'
// return a list of lists, where each list corresponds to a single category
function getDotsToRender(events) {
// each datetime needs to render as many dots as there are distinct
// categories in the events contained by the datetime.
// To this end, eventsByCategory is an intermediate data structure that
// groups a datetime's events by distinct categories
const eventsByCategory = {}
events.forEach(ev => {
if (eventsByCategory[ev.category]) {
eventsByCategory[ev.category].events.push((ev))
} else {
eventsByCategory[ev.category] = {
category: ev.category,
events: [ ev ]
}
}
})
return Object.values(eventsByCategory)
}
const TimelineEvents = ({
datetimes,
narrative,
getDatetimeX,
getCategoryY,
getCategoryColor,
onSelect,
transitionDuration,
styleDatetime
}) => {
function renderDatetime(datetime) {
if (narrative) {
const { steps } = narrative
// check all events in the datetime before rendering in narrative
let isInNarrative = false
for (let i = 0; i < datetime.events.length; i++) {
const event = datetime.events[i]
if (steps.map(s => s.id).includes(event.id)) {
isInNarrative = true
break;
}
}
if (!isInNarrative) {
return null
}
}
const dotsToRender = getDotsToRender(datetime.events)
return dotsToRender.map(dot => {
const customStyles = styleDatetime ? styleDatetime(datetime, dot.category) : null
const extraStyles = customStyles[0]
const extraRender = customStyles[1]
const styleProps = ({
fill: getCategoryColor(dot.category),
fillOpacity: 1,
transition: `transform ${transitionDuration / 1000}s ease`,
...extraStyles
})
return (
<DatetimeDot
onSelect={onSelect}
category={dot.category}
events={dot.events}
x={getDatetimeX(datetime)}
y={getCategoryY(dot.category)}
styleProps={styleProps}
extraRender={extraRender}
/>
)
})
}
return (
<g
clipPath={"url(#clip)"}
>
{datetimes.map(datetime => renderDatetime(datetime))}
</g>
);
}
export default TimelineEvents;

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,15 @@
import React from 'react';
const TimelineHeader = ({ title, date0, date1, onClick, hideInfo }) => (
<div className='timeline-header'>
<div className='timeline-toggle' onClick={() => onClick()}>
<p><i className='arrow-down'></i></p>
</div>
<div className={`timeline-info ${hideInfo ? 'hidden' : ''}`}>
<p>{title}</p>
<p>{date0} - {date1}</p>
</div>
</div>
);
export default TimelineHeader;

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

@@ -0,0 +1,31 @@
import React from 'react';
const TimelineMarkers = ({ getEventX, getCategoryY, transitionDuration, selected }) => {
function renderMarker(event) {
return (
<circle
className="timeline-marker"
cx={0}
cy={0}
style={{
'transform': `translate(${getEventX(event)}px, ${getCategoryY(event.category)}px)`,
'-webkit-transition': `transform ${transitionDuration / 1000}s ease`,
'-moz-transition': 'none',
'opacity': 0.9
}}
r="10"
>
</circle>
)
}
return (
<g
clipPath={"url(#clip)"}
>
{selected.map(event => renderMarker(event))}
</g>
);
}
export default TimelineMarkers;

View File

@@ -0,0 +1,25 @@
import React from 'react';
const TimelineZoomControls = ({ extent, zoomLevels, dims, onApplyZoom }) => {
function renderZoom(zoom, idx) {
const isActive = (zoom.duration === extent)
return (
<text
className={`zoom-level-button ${isActive ? 'active' : ''}`}
x="60"
y={(idx * 15) + 20}
onClick={() => onApplyZoom(zoom)}
>
{zoom.label}
</text>
)
}
return (
<g transform={`translate(${dims.width - dims.width_controls}, 0)`}>
{zoomLevels.map((z, idx) => renderZoom(z, idx))}
</g>
);
}
export default TimelineZoomControls;