mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-08 03:18:36 +03:00
Merge pull request #81 from forensic-architecture/topic/restyle-narrative-card
Topic/restyle narrative card
This commit is contained in:
@@ -9,15 +9,13 @@ import LoadingOverlay from './presentational/LoadingOverlay'
|
||||
import Map from './Map.jsx'
|
||||
import Toolbar from './Toolbar.jsx'
|
||||
import CardStack from './CardStack.jsx'
|
||||
import NarrativeCard from './NarrativeCard.js'
|
||||
import NarrativeControls from './presentational/NarrativeControls.js'
|
||||
import InfoPopUp from './InfoPopup.jsx'
|
||||
import Timeline from './Timeline.jsx'
|
||||
import Notification from './Notification.jsx'
|
||||
|
||||
import { parseDate } from '../js/utilities'
|
||||
|
||||
import { injectNarrative } from '../js/utilities'
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@@ -119,13 +117,6 @@ class Dashboard extends React.Component {
|
||||
getCategoryColor: category => this.getCategoryColor(category)
|
||||
}}
|
||||
/>
|
||||
<NarrativeCard
|
||||
methods={{
|
||||
onNext: () => this.moveInNarrative(1),
|
||||
onPrev: () => this.moveInNarrative(-1),
|
||||
onSelectNarrative: this.setNarrative
|
||||
}}
|
||||
/>
|
||||
<CardStack
|
||||
onViewSource={this.handleViewSource}
|
||||
onSelect={this.handleSelect}
|
||||
@@ -134,6 +125,17 @@ class Dashboard extends React.Component {
|
||||
getNarrativeLinks={event => this.getNarrativeLinks(event)}
|
||||
getCategoryColor={category => this.getCategoryColor(category)}
|
||||
/>
|
||||
<NarrativeControls
|
||||
narrative={!!app.narrative ? {
|
||||
...app.narrative,
|
||||
current: app.narrativeState.current
|
||||
} : null}
|
||||
methods={{
|
||||
onNext: () => this.moveInNarrative(1),
|
||||
onPrev: () => this.moveInNarrative(-1),
|
||||
onSelectNarrative: this.setNarrative
|
||||
}}
|
||||
/>
|
||||
<InfoPopUp
|
||||
ui={ui}
|
||||
app={app}
|
||||
@@ -167,16 +169,6 @@ function mapDispatchToProps(dispatch) {
|
||||
}
|
||||
}
|
||||
|
||||
function injectSource(id) {
|
||||
return state => ({
|
||||
...state,
|
||||
app: {
|
||||
...state.app,
|
||||
source: state.domain.sources[id]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => state,
|
||||
mapDispatchToProps,
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { selectActiveNarrative } from '../selectors'
|
||||
|
||||
function NarrativeCard ({ narrative, methods }) {
|
||||
const { onSelectNarrative, onNext, onPrev } = methods
|
||||
function renderClose() {
|
||||
return (
|
||||
<button
|
||||
className='side-menu-burg is-active'
|
||||
onClick={() => { onSelectNarrative(null) }}
|
||||
>
|
||||
<span></span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function _renderActions(current, steps) {
|
||||
const prevExists = current !== 0
|
||||
const nextExists = current < steps.length - 1
|
||||
return (
|
||||
<div className='actions'>
|
||||
<div
|
||||
className={`${prevExists ? '' : 'disabled'} action`}
|
||||
onClick={prevExists ? onPrev : null}>←
|
||||
</div>
|
||||
<div
|
||||
className={`${nextExists ? '' : 'disabled'} action`}
|
||||
onClick={nextExists ? onNext : null}>→
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// no display if no narrative
|
||||
if (!narrative) return null
|
||||
|
||||
const { steps, current } = narrative
|
||||
|
||||
if (steps[current]) {
|
||||
const step = steps[current]
|
||||
|
||||
return (
|
||||
<div className='narrative-info'>
|
||||
{renderClose()}
|
||||
<h3>{narrative.label}</h3>
|
||||
<p>{narrative.description}</p>
|
||||
<h6>
|
||||
<i className='material-icons left'>location_on</i>
|
||||
{current + 1}/{steps.length}. {step.location}
|
||||
</h6>
|
||||
{_renderActions(current, steps)}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
narrative: selectActiveNarrative(state)
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps)(NarrativeCard)
|
||||
@@ -28,7 +28,7 @@ class Timeline extends React.Component {
|
||||
height_controls: 115,
|
||||
margin_left: 120,
|
||||
margin_top: 20,
|
||||
trackHeight: 80
|
||||
trackHeight: 80
|
||||
},
|
||||
scaleX: null,
|
||||
scaleY: null,
|
||||
@@ -228,7 +228,7 @@ class Timeline extends React.Component {
|
||||
transitionDuration={this.state.transitionDuration}
|
||||
scaleX={this.state.scaleX}
|
||||
/>
|
||||
<TimelineCategories
|
||||
<TimelineCategories
|
||||
dims={dims}
|
||||
onDragStart={() => { this.onDragStart() }}
|
||||
onDrag={() => { this.onDrag() }}
|
||||
@@ -264,7 +264,7 @@ class Timeline extends React.Component {
|
||||
onSelect={this.props.methods.onSelect}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
16
src/components/presentational/NarrativeAdjust.js
Normal file
16
src/components/presentational/NarrativeAdjust.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export default ({ isDisabled, direction, onClickHandler }) => {
|
||||
return (
|
||||
<div
|
||||
className={`narrative-adjust ${direction}`}
|
||||
onClick={!isDisabled ? onClickHandler : null}
|
||||
>
|
||||
<i
|
||||
className={`material-icons ${isDisabled ? 'disabled' : ''}`}
|
||||
>
|
||||
{`chevron_${direction}`}
|
||||
</i>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
src/components/presentational/NarrativeCard.js
Normal file
40
src/components/presentational/NarrativeCard.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { selectActiveNarrative } from '../../selectors'
|
||||
|
||||
function NarrativeCard ({ narrative }) {
|
||||
// no display if no narrative
|
||||
const { steps, current } = narrative
|
||||
|
||||
if (steps[current]) {
|
||||
const step = steps[current]
|
||||
|
||||
return (
|
||||
<div className='narrative-info'>
|
||||
<div className='narrative-info-header'>
|
||||
<div className='count-container'>
|
||||
<div className='count'>
|
||||
{current + 1}/{steps.length}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{narrative.label}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <i className='material-icons left'>location_on</i> */}
|
||||
{/* {_renderActions(current, steps)} */}
|
||||
<p className='narrative-info-desc'>{narrative.description}</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
narrative: selectActiveNarrative(state)
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps)(NarrativeCard)
|
||||
17
src/components/presentational/NarrativeClose.js
Normal file
17
src/components/presentational/NarrativeClose.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
export default ({ onClickHandler, closeMsg }) => {
|
||||
return (
|
||||
<div
|
||||
className='narrative-close'
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
<button
|
||||
className='side-menu-burg is-active'
|
||||
>
|
||||
<span></span>
|
||||
</button>
|
||||
<div className='close-text'>{closeMsg}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
32
src/components/presentational/NarrativeControls.js
Normal file
32
src/components/presentational/NarrativeControls.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import NarrativeCard from './NarrativeCard'
|
||||
import NarrativeAdjust from './NarrativeAdjust'
|
||||
import NarrativeClose from './NarrativeClose'
|
||||
|
||||
export default ({ narrative, methods }) => {
|
||||
if (!narrative) return null
|
||||
|
||||
const { current, steps } = narrative
|
||||
const prevExists = current !== 0
|
||||
const nextExists = current < steps.length - 1
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<NarrativeCard narrative={narrative} />
|
||||
<NarrativeAdjust
|
||||
isDisabled={!prevExists}
|
||||
direction='left'
|
||||
onClickHandler={methods.onPrev}
|
||||
/>
|
||||
<NarrativeAdjust
|
||||
isDisabled={!nextExists}
|
||||
direction='right'
|
||||
onClickHandler={methods.onNext}
|
||||
/>
|
||||
<NarrativeClose
|
||||
onClickHandler={() => methods.onSelectNarrative(null)}
|
||||
closeMsg='-- exit from narrative --'
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@@ -42,10 +42,6 @@
|
||||
margin: 5px 0 10px 0;
|
||||
padding-bottom: 10px;
|
||||
|
||||
&.details {
|
||||
border-bottom: 1px solid $lightwhite;
|
||||
}
|
||||
|
||||
.card-cell {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ $timeline-height: 170px;
|
||||
color: white;
|
||||
|
||||
&.narrative-mode {
|
||||
right: auto;
|
||||
left: 10px;
|
||||
top: $narrative-info-max-height + 12px;
|
||||
height: calc(100% - #{$narrative-info-max-height} - #{$timeline-height} - 12px);
|
||||
right: 10px;
|
||||
left: auto;
|
||||
top: $narrative-info-max-height + 12px + 20px;
|
||||
height: calc(100% - #{$narrative-info-max-height} - #{$timeline-height} - 12px - 20px);
|
||||
}
|
||||
|
||||
&.full-height {
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
$narrative-info-width: 370px;
|
||||
$timeline-height: 170px;
|
||||
|
||||
/*
|
||||
NARRATIVE INFO
|
||||
*/
|
||||
.narrative-info {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
// height: auto;
|
||||
top: 30px;
|
||||
left: auto;
|
||||
right: 10px;
|
||||
height: 170px;
|
||||
width: $narrative-info-width;
|
||||
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;
|
||||
|
||||
.narrative-info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
border-bottom: 1px solid $darkwhite;
|
||||
padding: 0 15px;
|
||||
|
||||
.count-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-right: 1px solid $darkwhite;
|
||||
}
|
||||
.count {
|
||||
position: relative;
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.narrative-info-desc {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
h3, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -75,3 +101,84 @@ NARRATIVE INFO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.narrative-adjust {
|
||||
position: fixed;
|
||||
// top: calc(50vh - 100pt);
|
||||
bottom: $timeline-height;
|
||||
// top: 0;
|
||||
right: auto;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
z-index: 15; // z-index of card-stack is 10
|
||||
|
||||
&.left {
|
||||
right: calc(#{$narrative-info-width} - 70px);
|
||||
}
|
||||
|
||||
&.right {
|
||||
// right: calc(#{$narrative-info-width} + 10px);
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 60pt;
|
||||
color: $offwhite;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&.disabled {
|
||||
color: $darkgrey;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: $darkgrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.narrative-close {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
position: fixed;
|
||||
padding: 2px 5px 0 5px;
|
||||
// right: $narrative-info-width - 15px - 10px;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
width: $narrative-info-width - 10px - 2px;
|
||||
// width: 15px;
|
||||
background-color: black;
|
||||
height: 20px;
|
||||
transition: background-color 0.2s ease;
|
||||
border: 1px solid black;
|
||||
|
||||
button {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.close-text {
|
||||
display: none;
|
||||
color: $midgrey;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
// disable whitening of crosshair on hover
|
||||
button {
|
||||
span, span:before, span:after {
|
||||
background: $midwhite !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: $offwhite;
|
||||
color: black;
|
||||
.close-text {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user