mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 05:18:34 +03:00
Create Narrative card
This commit is contained in:
@@ -171,6 +171,14 @@ export function updateTimeRange(timerange) {
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE';
|
||||
export function updateNarrative(narrative) {
|
||||
return {
|
||||
type: UPDATE_NARRATIVE,
|
||||
narrative
|
||||
}
|
||||
}
|
||||
|
||||
export const RESET_ALLFILTERS = 'RESET_ALLFILTERS'
|
||||
export function resetAllFilters() {
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@ import LoadingOverlay from './presentational/LoadingOverlay';
|
||||
import Viewport from './Viewport.jsx';
|
||||
import Toolbar from './Toolbar.jsx';
|
||||
import CardStack from './CardStack.jsx';
|
||||
import NarrativeCard from './NarrativeCard.js';
|
||||
import InfoPopUp from './InfoPopup.jsx';
|
||||
import Timeline from './Timeline.jsx';
|
||||
import Notification from './Notification.jsx';
|
||||
@@ -104,6 +105,9 @@ class Dashboard extends React.Component {
|
||||
app={this.props.app}
|
||||
toggle={() => this.props.actions.toggleInfoPopup()}
|
||||
/>
|
||||
<NarrativeCard
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
<Notification
|
||||
isNotification={this.props.ui.flags.isNotification}
|
||||
notifications={this.props.domain.notifications}
|
||||
|
||||
60
src/components/NarrativeCard.js
Normal file
60
src/components/NarrativeCard.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
class NarrativeCard extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
step: 0
|
||||
}
|
||||
}
|
||||
|
||||
goToPrevKeyFrame() {
|
||||
if (this.state.step > 0) {
|
||||
this.setState({ step: this.state.step - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
goToNextKeyFrame() {
|
||||
if (this.state.step < this.props.narrative.steps.length - 1) {
|
||||
this.setState({ step: this.state.step + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.narrative !== null) {
|
||||
const step = this.props.narrative.steps[this.state.step];
|
||||
this.props.onSelect([step.id]);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.narrative !== null) {
|
||||
const steps = this.props.narrative.steps;
|
||||
const step = steps[this.state.step];
|
||||
|
||||
return (
|
||||
<div className="narrative-info">
|
||||
<h6>{this.props.narrative.label}</h6>
|
||||
<p>{this.props.narrative.description}</p>
|
||||
<h3>{this.state.step + 1}/{steps.length}. {step.location}</h3>
|
||||
<div className="actions">
|
||||
<div className={`${(!this.state.step) ? 'disabled ' : ''} action`} onClick={() => this.goToPrevKeyFrame()}>←</div>
|
||||
<div className={`${(this.state.step >= this.props.narrative.steps.length - 1) ? 'disabled ' : ''} action`} onClick={() => this.goToNextKeyFrame()}>→</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (<div/>);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
console.log(state)
|
||||
return {
|
||||
narrative: state.app.narrative
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps)(NarrativeCard);
|
||||
@@ -34,19 +34,27 @@ class Toolbar extends React.Component {
|
||||
if (this.props.features.USE_SEARCH) {
|
||||
return (
|
||||
<TabPanel>
|
||||
<Search
|
||||
language={this.props.language}
|
||||
tags={this.props.tags}
|
||||
categories={this.props.categories}
|
||||
tagFilters={this.props.tagFilters}
|
||||
categoryFilters={this.props.categoryFilters}
|
||||
filter={this.props.filter}
|
||||
/>
|
||||
<Search
|
||||
language={this.props.language}
|
||||
tags={this.props.tags}
|
||||
categories={this.props.categories}
|
||||
tagFilters={this.props.tagFilters}
|
||||
categoryFilters={this.props.categoryFilters}
|
||||
filter={this.props.filter}
|
||||
/>
|
||||
</TabPanel>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
goToNarrative(narrative) {
|
||||
this.setState({
|
||||
tabNum: -1
|
||||
}, () => {
|
||||
this.props.actions.updateNarrative(narrative);
|
||||
});
|
||||
}
|
||||
|
||||
renderToolbarNarrativePanel() {
|
||||
return (
|
||||
<TabPanel>
|
||||
@@ -55,7 +63,7 @@ class Toolbar extends React.Component {
|
||||
{this.props.narratives.map((narr) => {
|
||||
return (
|
||||
<div className="panel-action action">
|
||||
<button style={{ backgroundColor: '#000' }}>
|
||||
<button style={{ backgroundColor: '#000' }} onClick={() => { this.goToNarrative(narr); }}>
|
||||
<p>{narr.label}</p>
|
||||
<p><small>{narr.description}</small></p>
|
||||
</button>
|
||||
|
||||
@@ -48,7 +48,6 @@ class ToolbarBottomActions extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="bottom-actions">
|
||||
<button onClick={() => { this.toggleGuidedMode(); }}>Toggle mode</button>
|
||||
{/*}{this.renderMapActions()}
|
||||
<div className="bottom-action-block">
|
||||
<button className="action-button tiny default" onClick={() => { this.toggleLanguage()}}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
UPDATE_SELECTED,
|
||||
UPDATE_TAGFILTERS,
|
||||
UPDATE_TIMERANGE,
|
||||
UPDATE_NARRATIVE,
|
||||
RESET_ALLFILTERS,
|
||||
TOGGLE_LANGUAGE,
|
||||
TOGGLE_MAPVIEW,
|
||||
@@ -24,6 +25,12 @@ function updateSelected(appState, action) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateNarrative(appState, action) {
|
||||
return Object.assign({}, appState, {
|
||||
narrative: action.narrative
|
||||
});
|
||||
}
|
||||
|
||||
function updateTagFilters(appState, action) {
|
||||
const tagFilters = appState.filters.tags.slice(0);
|
||||
const nextActiveState = action.tag.active
|
||||
@@ -113,6 +120,8 @@ function app(appState = initial.app, action) {
|
||||
return updateTagFilters(appState, action);
|
||||
case UPDATE_TIMERANGE:
|
||||
return updateTimeRange(appState, action);
|
||||
case UPDATE_NARRATIVE:
|
||||
return updateNarrative(appState, action);
|
||||
case RESET_ALLFILTERS:
|
||||
return resetAllFilters(appState, action);
|
||||
case TOGGLE_LANGUAGE:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@import 'loading';
|
||||
@import 'header';
|
||||
@import 'cardstack';
|
||||
@import 'narrativecard';
|
||||
@import 'map';
|
||||
@import 'timeline';
|
||||
@import 'tag-filters';
|
||||
|
||||
63
src/scss/narrativecard.scss
Normal file
63
src/scss/narrativecard.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
NARRATIVE INFO
|
||||
*/
|
||||
.narrative-info {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 130px;
|
||||
height: auto;
|
||||
width: 270px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
max-height: calc(100% - 250px);
|
||||
overflow: auto;
|
||||
box-shadow: 0 19px 38px rgba($black, 0.3), 0 15px 12px rgba($black, 0.22);
|
||||
background: $black;
|
||||
border: 1px solid $midgrey;
|
||||
color: $offwhite;
|
||||
font-family: 'Merriweather', 'Georgia', serif;
|
||||
|
||||
h3, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $large;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: 'Lato', 'Helvetica', sans-serif;
|
||||
font-size: $normal;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 100%;
|
||||
.action {
|
||||
width: calc(50% - 5px);
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
line-height: 40px;
|
||||
font-family: 'Lato', 'Helvetica', sans-serif;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
|
||||
&:not(.disabled) {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
color: $yellow;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $midgrey;
|
||||
cursor: normal;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const initial = {
|
||||
error: null,
|
||||
highlighted: null,
|
||||
selected: [],
|
||||
narrative: null,
|
||||
filters: {
|
||||
timerange: [
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"),
|
||||
|
||||
Reference in New Issue
Block a user