Merge branch 'develop' into feature/simplified-card-props

This commit is contained in:
Zac Ioannidis
2020-10-28 18:25:16 +00:00
10 changed files with 97 additions and 125 deletions

View File

@@ -305,6 +305,13 @@ export function toggleInfoPopup () {
}
}
export const TOGGLE_INTROPOPUP = 'TOGGLE_INTROPOPUP'
export function toggleIntroPopup () {
return {
type: TOGGLE_INTROPOPUP
}
}
export const TOGGLE_NOTIFICATIONS = 'TOGGLE_NOTIFICATIONS'
export function toggleNotifications () {
return {

View File

@@ -94,9 +94,11 @@
"default": {
"header": "Navigating the Platform",
"intro": [
"Open source research by [Bellingcat](https://bellingcat.com).<br/>Software and spatialisation by [Forensic Architecture](https://forensic-architecture.org).",
"Each event represents an occurence that is distinct in time or space, or both. An event is represented by a coloured circle on both the map and the timeline.",
"Select an event to reveal its content and sources. You can filter events by category or other specified filters in the top left toolbar."
"Each small **dot** represents a **datapoint**, or incident. Click on a dot to see details. Hover over a larger **cluster** dot to see how many events it represents.",
"Zoom in either with a mouse-scroll or by clicking a cluster dot.",
"Use **filters** and **categories** to segment the data. Selecting certain filters and categories will show only the datapoints that relate to them. If no filters or categories are selected, all the datapoints are displayed.",
"Selecting more than one filter will introduce colour-coded datapoints, which allow you to compare types of incident across time and space. This feature works up to a maximum of six filters.",
"Use the left and right arrows to move back and forward through time. Use the handles on the right to select a date range."
],
"notation": "Combinations of colours within a circle indicate multiple events in a single location.",
"arrows": "Use the left/right arrows on the keboard to move back and forth between events in time."

View File

@@ -1,110 +1,13 @@
import React from 'react'
import marked from 'marked'
import Popup from './presentational/Popup'
import copy from '../common/data/copy.json'
export default ({ ui, app, methods }) => {
function renderIntro () {
var introCopy = copy[app.language].legend.default.intro
if (process.env.store.text && process.env.store.text.introCopy) introCopy = process.env.store.text.introCopy
return introCopy.map(txt => <p dangerouslySetInnerHTML={{ __html: marked(txt) }} />)
}
function renderHalfWithDot () {
// extract category colors from store for combined display.
const categoryKeys = Object.keys(ui.style.categories)
let firstFill = 'red'
let secondFill = 'blue'
if (categoryKeys.length >= 1) {
firstFill = ui.style.categories[categoryKeys[0]]
}
if (categoryKeys.length >= 2) {
secondFill = ui.style.categories[categoryKeys[1]]
}
return [
<style>{`.svg-demo { max-width: 30px } .first { fill: ${firstFill} } .second { fill: ${secondFill} } .demo-text { font-size: 9pt; color: white; font-weight:900 }`}</style>,
<svg viewBox='0 0 30 30' className='svg-demo'>
<g className='location demo-element' transform='translate(15,15)'>
<path className='location-event-marker first' id='arc_0' d='M 10 0 A 10 10 0 0 1 -10 1.2246467991473533e-15 L 0 0 L 10 0 Z' />
<path class='location-event-marker second' id='arc_1' d='M -10 1.2246467991473533e-15 A 10 10 0 0 1 10 -2.4492935982947065e-15 L 0 0 L -10 1.2246467991473533e-15 Z' />
<text class='location-count demo-text' dx='-4' dy='4'>2</text>
</g>
</svg>
]
}
function renderCategoryColors () {
const categories = Object.keys(ui.style.categories).filter(label => label !== 'default')
categories.reverse()
return categories.map(category => (
<div className='legend-section'>
<svg x='0px' y='0px' width='50px' height='20px' viewBox='0 0 100 30' enableBackground='new 0 0 100 30'>
<circle opacity='1' fill={ui.style.categories[category]} cx='50' cy='15' r='15' />
</svg>
<div className='legend-labels'>
<div className='label'>{category}</div>
</div>
</div>
))
}
function renderArrow (strokeFill) {
return (
<svg x='-10px' y='0px' width='100px' height='30px' viewBox='0 40 100 30' enableBackground='new 0 0 100 70'>
<polyline fill='none' stroke={strokeFill} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' stroke-miterlimit='10' points='
8.376,63.723 47.287,63.723 60,46 80,46 ' />
<line stroke={strokeFill} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='78.849' y1='41.94' x2='84.195' y2='46' />
<line stroke={strokeFill} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' x1='78.849' y1='50.06' x2='84.195' y2='46' />
</svg>
)
}
function renderView2DLegend () {
return (
<div className={`infopopup ${(app.flags.isInfopopup) ? '' : 'hidden'}`}>
<div className='legend-header'>
<button onClick={methods.onClose} className='side-menu-burg over-white is-active'><span /></button>
<h2>{copy[app.language].legend.default.header}</h2>
</div>
{renderIntro()}
<div>
{renderCategoryColors()}
</div>
<br />
<div className='legend'>
<div className='legend-container'>
<div className='legend-item one'>
{renderHalfWithDot()}
</div>
<div className='legend-item three'>
{copy[app.language].legend.default.notation}
</div>
</div>
</div>
<br />
<div>
<p>{copy[app.language].legend.default.arrows}</p>
</div>
{
ui.style.arrows ? (
Object.keys(ui.style.arrows).map(arrowName => (
<div className='legend-section'>
{renderArrow(ui.style.arrows[arrowName])}
<div className='legend-labels'>
<div className='label'>{arrowName}</div>
</div>
</div>
))
) : null
}
</div>
)
}
return (
<div>{renderView2DLegend()}</div>
)
}
export default ({ isOpen, onClose, language, styles }) => (
<Popup
title={copy[language].legend.default.header}
content={copy[language].legend.default.intro}
onClose={onClose}
isOpen={isOpen}
styles={styles}
/>
)

View File

@@ -13,7 +13,8 @@ import Toolbar from './Toolbar/Layout'
import CardStack from './CardStack.jsx'
// import {CardStack} from '@forensic-architecture/design-system'
import NarrativeControls from './presentational/Narrative/Controls.js'
import InfoPopUp from './InfoPopup.jsx'
import InfoPopup from './InfoPopup.jsx'
import Popup from './presentational/Popup'
import Timeline from './Timeline.jsx'
import Notification from './Notification.jsx'
import StateOptions from './StateOptions.jsx'
@@ -239,7 +240,7 @@ class Dashboard extends React.Component {
}
render () {
const { actions, app, domain, ui, features } = this.props
const { actions, app, domain, features } = this.props
if (isMobile || window.innerWidth < 600) {
const msg = 'This platform is not suitable for mobile. Please re-visit the site on a device with a larger screen.'
return (
@@ -259,6 +260,13 @@ class Dashboard extends React.Component {
)
}
const popupStyles = {
fontSize: 24,
height: `calc(100vh - ${app.timeline.dimensions.height}px)`,
width: '40vw',
bottom: app.timeline.dimensions.height
}
return (
<div>
<Toolbar
@@ -310,12 +318,19 @@ class Dashboard extends React.Component {
onSelectNarrative: this.setNarrative
}}
/>
<InfoPopUp
ui={ui}
app={app}
methods={{
onClose: actions.toggleInfoPopup
}}
<InfoPopup
language={app.language}
styles={popupStyles}
isOpen={app.flags.isInfopopup}
onClose={actions.toggleInfoPopup}
/>
<Popup
title={process.env.display_title}
theme='dark'
isOpen={app.flags.isIntropopup}
onClose={actions.toggleIntroPopup}
content={app.intro}
styles={popupStyles}
/>
{app.debug ? <Notification
isNotification={app.flags.isNotification}

View File

@@ -176,7 +176,9 @@ class TemplateCover extends React.Component {
</div>
</div>
<div className='md-container' dangerouslySetInnerHTML={{ __html: marked(this.props.cover.description) }} />
{Array.isArray(this.props.cover.description)
? this.props.cover.description.map(e => <div className='md-container' dangerouslySetInnerHTML={{ __html: marked(e) }} />)
: <div className='md-container' dangerouslySetInnerHTML={{ __html: marked(this.props.cover.description) }} />}
{videos ? (
<div className='hero'>

View File

@@ -0,0 +1,21 @@
import React from 'react'
import marked from 'marked'
export default ({
content = [],
styles = {},
isOpen = true,
onClose,
title,
theme = 'light'
}) => (
<div>
<div className={`infopopup ${isOpen ? '' : 'hidden'} ${theme === 'dark' ? 'dark' : 'light'}`} style={styles}>
<div className='legend-header'>
<button onClick={onClose} className='side-menu-burg over-white is-active'><span /></button>
<h2>{title}</h2>
</div>
{content.map(t => <div dangerouslySetInnerHTML={{ __html: marked(t) }} />)}
</div>
</div>
)

View File

@@ -18,6 +18,7 @@ import {
TOGGLE_FETCHING_DOMAIN,
TOGGLE_FETCHING_SOURCES,
TOGGLE_INFOPOPUP,
TOGGLE_INTROPOPUP,
TOGGLE_NOTIFICATIONS,
TOGGLE_COVER,
FETCH_ERROR,
@@ -205,6 +206,7 @@ const toggleSites = toggleFlagAC('isShowingSites')
const toggleFetchingDomain = toggleFlagAC('isFetchingDomain')
const toggleFetchingSources = toggleFlagAC('isFetchingSources')
const toggleInfoPopup = toggleFlagAC('isInfopopup')
const toggleIntroPopup = toggleFlagAC('isIntropopup')
const toggleNotifications = toggleFlagAC('isNotification')
const toggleCover = toggleFlagAC('isCover')
@@ -287,6 +289,8 @@ function app (appState = initial.app, action) {
return toggleFetchingSources(appState)
case TOGGLE_INFOPOPUP:
return toggleInfoPopup(appState)
case TOGGLE_INTROPOPUP:
return toggleIntroPopup(appState)
case TOGGLE_NOTIFICATIONS:
return toggleNotifications(appState)
case TOGGLE_COVER:

View File

@@ -1,6 +1,7 @@
$event_default: red;
$offwhite: #efefef;
$offwhite-transparent: rgba(239,239,239, 0.9);
$lightwhite: #dfdfdf;
$midwhite: #a0a0a0;
$darkwhite: darken($midwhite, 15%);
@@ -10,6 +11,7 @@ $green: rgb(61, 241, 79);
$midgrey: rgb(44, 44, 44);
$darkgrey: #232323;
$black: #000000;
$black-transparent: rgba(0,0,0,0.7);
// Category colors
$default: red;

View File

@@ -5,7 +5,7 @@
box-shadow: 10px -10px 38px rgba(0, 0, 0, 0.3), 10px 15px 12px rgba(0, 0, 0, 0.22);
color: $darkgrey;
position: absolute;
background: $offwhite;
background: $offwhite-transparent;
bottom: $timeline-height;
left: $toolbar-width;
border: 3px solid $offwhite;
@@ -23,6 +23,24 @@
opacity: 0;
}
.side-menu-burg {
position: absolute;
right: 8px;
top: 10px;
&.light {
&.is-active span:after,
&.is-active span:before {
background: black;
}
}
}
&.dark {
background: $black-transparent;
color: white;
}
.legend {
display: flex;
flex-direction: column;
@@ -38,11 +56,6 @@
margin: 0;
}
}
.side-menu-burg {
position: absolute;
right: 8px;
top: 10px;
}
.legend-container {
height: 100%;

View File

@@ -93,6 +93,7 @@ const initial = {
isCover: true,
isCardstack: true,
isInfopopup: false,
isIntropopup: false,
isShowingSites: true
},
cover: {
@@ -170,4 +171,6 @@ if (process.env.store) {
appStore.app.timeline.range[0] = new Date(appStore.app.timeline.range[0])
appStore.app.timeline.range[1] = new Date(appStore.app.timeline.range[1])
appStore.app.flags.isIntropopup = !!appStore.app.intro
export default appStore