Merge pull request #98 from forensic-architecture/topic/fixes

Add key and optional cover
This commit is contained in:
Lachlan Kermode
2019-02-07 14:54:29 +00:00
committed by GitHub
16 changed files with 191 additions and 205 deletions

5
.gitignore vendored
View File

@@ -3,5 +3,10 @@ build/
node_modules/
config.js
dev.config.js
# ignore all covers but the default
src/components/presentational/covers/
!src/src/components/presentational/covers/Default.js
src/\.DS_Store
\.DS_Store

View File

@@ -11,6 +11,7 @@ module.exports = {
INCOMING_DATETIME_FORMAT: '%m/%d/%YT%H:%M',
MAPBOX_TOKEN: 'pk.EXAMPLE_MAPBOX_TOKEN',
features: {
USE_COVER: false,
USE_TAGS: false,
USE_SEARCH: false,
USE_SITES: true,
@@ -32,16 +33,15 @@ module.exports = {
new Date(2014, 5, 9),
new Date(2018, 1, 6, 23)
]
} }
},
ui: {
style: {
categories: {},
shapes: {},
narratives: {},
selectedEvent: {},
}
}
},
ui: {
style: {
categories: {},
shapes: {},
narratives: {},
selectedEvent: {}
}
}
}

View File

@@ -291,6 +291,13 @@ export function markNotificationsRead () {
}
}
export const TOGGLE_COVER = 'TOGGLE_COVER'
export function toggleCover () {
return {
type: TOGGLE_COVER
}
}
// ERRORS
export const FETCH_SOURCE_ERROR = 'FETCH_SOURCE_ERROR'

View File

@@ -11,7 +11,6 @@ export default (props) => {
function renderCategoryTree () {
return (
<div>
<h2>{copy[props.language].toolbar.categories}</h2>
{props.categories.map(cat => {
return (<li
key={cat.category.replace(/ /g, '_')}
@@ -31,7 +30,7 @@ export default (props) => {
return (
<div className='react-innertabpanel'>
<h2>{copy[props.language].toolbar.explore_by_category__title}</h2>
<h2>{copy[props.language].toolbar.categories}</h2>
<p>{copy[props.language].toolbar.explore_by_category__description}</p>
{renderCategoryTree()}
</div>

View File

@@ -13,6 +13,8 @@ import NarrativeControls from './presentational/Narrative/Controls.js'
import InfoPopUp from './InfoPopup.jsx'
import Timeline from './Timeline.jsx'
import Notification from './Notification.jsx'
import StaticPage from './StaticPage'
import DefaultCover from './presentational/covers/Default'
import { parseDate } from '../js/utilities'
@@ -139,7 +141,9 @@ class Dashboard extends React.Component {
<InfoPopUp
ui={ui}
app={app}
toggle={() => actions.toggleInfoPopup()}
methods={{
onClose: actions.toggleInfoPopup
}}
/>
<Notification
isNotification={app.flags.isNotification}
@@ -155,6 +159,13 @@ class Dashboard extends React.Component {
}
/>
) : null}
{process.env.features.USE_COVER && (
<StaticPage showing={app.flags.isCover}>
{/* enable USE_COVER in config.js features, and customise your header */}
{/* pass 'actions.toggleCover' as a prop to your custom header */}
<DefaultCover showAppHandler={actions.toggleCover} />
</StaticPage>
)}
<LoadingOverlay
ui={app.flags.isFetchingDomain}
language={app.language}

View File

@@ -1,84 +1,44 @@
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 ({ ui, app, methods }) => {
function renderIntro () {
return copy[app.language].legend.default.intro.map(txt => <p>{txt}</p>)
}
renderCategoryColors () {
const colors = copy[this.props.app.language].legend.view2d.colors.slice(0)
colors.reverse()
return (
<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}`} />
{color.label}
</div>
)
})}
function renderCategoryColors () {
const categories = Object.keys(ui.style.categories).filter(label => label !== 'default')
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>
)
))
}
renderView2DLegend () {
function 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>
{this.renderView2DCopy()}
<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 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' />
</svg>
<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' />
</svg>
<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' />
</svg>
<div className='legend-labels'>
<div className='label'>Rutas de bus</div>
</div>
<div className='legend-container'>
{renderCategoryColors()}
</div>
</div>
</div>
)
}
render () {
return (
<div>{this.renderView2DLegend()}</div>
)
}
return (
<div>{renderView2DLegend()}</div>
)
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
export default ({ showing, children }) => {
return (
<div className={`cover-container ${showing ? 'showing' : ''}`}>
{children}
</div>
)
}

View File

@@ -63,7 +63,6 @@ class TagListPanel extends React.Component {
renderTree () {
return (
<div>
<h2>{copy[this.props.language].toolbar.tags}</h2>
{this.state.treeComponents.map(c => c)}
</div>
)
@@ -72,7 +71,7 @@ class TagListPanel extends React.Component {
render () {
return (
<div className='react-innertabpanel'>
<h2>{copy[this.props.language].toolbar.explore_by_tag__title}</h2>
<h2>{copy[this.props.language].toolbar.tags}</h2>
<p>{copy[this.props.language].toolbar.explore_by_tag__description}</p>
{this.renderTree()}
</div>

View File

@@ -0,0 +1,16 @@
import React from 'react'
export default ({ showAppHandler }) => (
<div className='default-cover-container'>
<h3>Here's an example cover.</h3>
<p>Replace it with a more descriptive one:</p>
<ul>
<li>Create a new component in <code>components/presentational/covers</code>.</li>
<li>Import in in <code>components/Dashboard.jsx</code> in the <code>render</code> function.</li>
</ul>
<br /><br />
<div>
<button onClick={showAppHandler}>Go to app</button>
</div>
</div>
)

View File

@@ -90,6 +90,14 @@
{ "class": "category_group03", "label": "Category Group 03" },
{ "class": "other", "label": "Other categories" }
]
},
"default": {
"header": "Navigating the Platform",
"intro": [
"Each event represents an occurence that is distinct in either time, 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.",
"Narratives compose events to reveal logical threads that emerge from them. Transition to narrative mode by selecting a narrative from the top left dashboard icon."
]
}
},
"toolbar": {

View File

@@ -126,3 +126,13 @@ export function urlFromEnv (ext) {
return null
}
}
export function toggleFlagAC (flag) {
return (appState) => ({
...appState,
flags: {
...appState.flags,
[flag]: !appState.flags[flag]
}
})
}

View File

@@ -1,6 +1,6 @@
/* global d3 */
import initial from '../store/initial.js'
import { parseDate } from '../js/utilities'
import { parseDate, toggleFlagAC } from '../js/utilities'
import {
UPDATE_HIGHLIGHTED,
@@ -19,6 +19,7 @@ import {
TOGGLE_FETCHING_SOURCES,
TOGGLE_INFOPOPUP,
TOGGLE_NOTIFICATIONS,
TOGGLE_COVER,
FETCH_ERROR,
FETCH_SOURCE_ERROR
} from '../actions'
@@ -189,16 +190,6 @@ function toggleLanguage (appState, action) {
})
}
function toggleSites (appState, action) {
return {
...appState,
flags: {
...appState.flags,
isShowingSites: !appState.flags.isShowingSites
}
}
}
function updateSource (appState, action) {
return {
...appState,
@@ -214,37 +205,12 @@ function fetchError (state, action) {
}
}
function toggleFetchingDomain (appState, action) {
return Object.assign({}, appState, {
flags: Object.assign({}, appState.flags, {
isFetchingDomain: !appState.flags.isFetchingDomain
})
})
}
function toggleFetchingSources (appState, action) {
return Object.assign({}, appState, {
flags: Object.assign({}, appState.flags, {
isFetchingSources: !appState.flags.isFetchingSources
})
})
}
function toggleInfoPopup (appState, action) {
return Object.assign({}, appState, {
flags: Object.assign({}, appState.flags, {
isInfopopup: !appState.flags.isInfopopup
})
})
}
function toggleNotifications (appState, action) {
return Object.assign({}, appState, {
flags: Object.assign({}, appState.flags, {
isNotification: !appState.flags.isNotification
})
})
}
const toggleSites = toggleFlagAC('isShowingSites')
const toggleFetchingDomain = toggleFlagAC('isFetchingDomain')
const toggleFetchingSources = toggleFlagAC('isFetchingSources')
const toggleInfoPopup = toggleFlagAC('isInfopopup')
const toggleNotifications = toggleFlagAC('isNotification')
const toggleCover = toggleFlagAC('isCover')
function fetchSourceError (appState, action) {
return {
@@ -278,20 +244,24 @@ function app (appState = initial.app, action) {
return updateSource(appState, action)
case RESET_ALLFILTERS:
return resetAllFilters(appState, action)
/* toggles */
case TOGGLE_LANGUAGE:
return toggleLanguage(appState, action)
case TOGGLE_SITES:
return toggleSites(appState, action)
return toggleSites(appState)
case TOGGLE_FETCHING_DOMAIN:
return toggleFetchingDomain(appState)
case TOGGLE_FETCHING_SOURCES:
return toggleFetchingSources(appState)
case TOGGLE_INFOPOPUP:
return toggleInfoPopup(appState)
case TOGGLE_NOTIFICATIONS:
return toggleNotifications(appState)
case TOGGLE_COVER:
return toggleCover(appState)
/* errors */
case FETCH_ERROR:
return fetchError(appState, action)
case TOGGLE_FETCHING_DOMAIN:
return toggleFetchingDomain(appState, action)
case TOGGLE_FETCHING_SOURCES:
return toggleFetchingSources(appState, action)
case TOGGLE_INFOPOPUP:
return toggleInfoPopup(appState, action)
case TOGGLE_NOTIFICATIONS:
return toggleNotifications(appState, action)
case FETCH_SOURCE_ERROR:
return fetchSourceError(appState, action)
default:

26
src/scss/cover.scss Normal file
View File

@@ -0,0 +1,26 @@
.cover-container {
position: absolute;
top: -100%;
left: 0;
background-color: black;
width: 100%;
height: 100%;
opacity: 0.95;
transition: top 0.4s ease;
z-index: $overheader + 1;
color: $offwhite;
&.showing {
top: 0;
left: 0;
}
}
.default-cover-container {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
height: 100%;
}

View File

@@ -24,87 +24,52 @@
opacity: 0;
}
.legend-section {
width: 300px;
padding-left: 60px;
height: 40px;
display: inline-block;
svg {
width: 100px;
float: left;
display: inline-block;
}
.legend-labels {
float: left;
display: inline-block;
width: calc(100% - 100px);
.label {
display: block;
font-size: $xsmall;
margin-top: 10px;
margin-left: 10px;
.color-category {
width: 8px;
height: 8px;
border-radius: 10px;
display: inline-block;
margin: 0px 5px 0 0;
}
}
}
&:first-child {
.legend-labels .label {
margin-top: 0;
}
}
.legend {
display: flex;
flex-direction: column;
}
.legend-header {
display: flex;
flex-direction: row;
h2 {
display: flex;
font-size: 12pt;
letter-spacing: 2px;
margin: 0;
}
}
.side-menu-burg {
position: absolute;
right: 8px;
top: 10px;
}
.legend-item {
display: block;
width: 100%;
display: inline-block;
margin-bottom: 3px;
padding-left: 80px;
.legend-container {
height: 100%;
display: flex;
flex-direction: column;
}
.item-label {
line-height: 15px;
height: 15px;
font-size: $normal;
}
.legend-section {
width: 300px;
height: 25px;
display: flex;
align-items: center;
.color-marker {
display: inline-block;
width: 15px;
height: 15px;
svg {
width: 60px;
float: left;
margin: 0 10px 0 0;
border-radius: 15px;
&.victims { background-color: #C90500; }
&.military { background-color: #319C31; }
&.nonstate { background-color: #AC28AC; }
&.state-police { background-color: #0000BF; }
&.iguala-municipal-police { background-color: #00558D; }
&.federal-police { background-color: #5756A2; }
&.huitzuco-municipal-police { background-color: #4ECAC1; }
&.cocula-municipal-police { background-color: #095959; }
&.ambulance { background-color: #ffffff; }
&.other { background-color: #D3CE2A; }
&.drivers { background-color: #822519; }
&.communications { background-color: #a6a6a6; }
&.GIEI { background-color: #ffffff; }
&.PGR { background-color: #000000; }
display: inline-block;
}
.legend-labels {
display: flex;
.label {
font-size: $xsmall;
}
}
}
}

View File

@@ -16,3 +16,4 @@
@import 'notification';
@import 'scene';
@import 'mediaplayer';
@import 'cover';

View File

@@ -52,7 +52,7 @@ const initial = {
map: {
anchor: [31.356397, 34.784818],
startZoom: 11,
minZoom: 7,
minZoom: 6,
maxZoom: 18,
bounds: null,
maxBounds: [[180, -180], [-180, 180]]
@@ -84,9 +84,9 @@ const initial = {
flags: {
isFetchingDomain: false,
isFetchingSources: false,
isCover: true,
isCardstack: true,
isInfopopup: false,
isInfopopup: true,
isShowingSites: true
}
},