mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-11 21:08:36 +03:00
synchronous updateSource logic
This commit is contained in:
148
src/components/:w
Normal file
148
src/components/:w
Normal file
@@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as actions from '../actions';
|
||||
import * as selectors from '../selectors';
|
||||
|
||||
import MediaOverlay from './presentational/MediaOverlay';
|
||||
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';
|
||||
|
||||
import { parseDate } from '../js/utilities';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleViewSource = this.handleViewSource.bind(this)
|
||||
this.handleHighlight = this.handleHighlight.bind(this)
|
||||
this.handleSelect = this.handleSelect.bind(this)
|
||||
this.handleTagFilter = this.handleTagFilter.bind(this)
|
||||
this.updateTimerange = this.updateTimerange.bind(this)
|
||||
|
||||
this.eventsById = {}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.app.isMobile) {
|
||||
this.props.actions.fetchDomain()
|
||||
.then(domain => this.props.actions.updateDomain(domain));
|
||||
}
|
||||
}
|
||||
|
||||
handleHighlight(highlighted) {
|
||||
this.props.actions.updateHighlighted((highlighted) ? highlighted : null);
|
||||
}
|
||||
|
||||
getEventById(eventId) {
|
||||
if (this.eventsById[eventId]) return this.eventsById[eventId];
|
||||
this.eventsById[eventId] = this.props.domain.events.find(ev => ev.id === eventId);
|
||||
return this.eventsById[eventId];
|
||||
}
|
||||
|
||||
handleViewSource(source) {
|
||||
console.log('handleViewSource: to implement in Dashboard.jsx')
|
||||
this.props.actions.updateSource(source)
|
||||
}
|
||||
|
||||
handleSelect(selected) {
|
||||
if (selected) {
|
||||
let eventsToSelect = selected.map(event => this.getEventById(event.id));
|
||||
eventsToSelect = eventsToSelect.sort((a, b) => parseDate(a.timestamp) - parseDate(b.timestamp))
|
||||
|
||||
this.props.actions.updateSelected(eventsToSelect)
|
||||
}
|
||||
}
|
||||
|
||||
handleTagFilter(tag) {
|
||||
this.props.actions.updateTagFilters(tag);
|
||||
}
|
||||
|
||||
updateTimerange(timeRange) {
|
||||
this.props.actions.updateTimeRange(timeRange);
|
||||
}
|
||||
|
||||
getCategoryColor(category='other') {
|
||||
return this.props.ui.style.categories[category] || this.props.ui.style.categories['other']
|
||||
}
|
||||
|
||||
getNarrativeLinks(event) {
|
||||
const narrative = this.props.domain.narratives.find(nv => nv.id === event.narrative);
|
||||
if (narrative) return narrative.byId[event.id];
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Viewport
|
||||
methods={{
|
||||
onSelect: this.handleSelect,
|
||||
getCategoryColor: category => this.getCategoryColor(category)
|
||||
}}
|
||||
/>
|
||||
<Toolbar
|
||||
onFilter={this.handleTagFilter}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
<CardStack
|
||||
onViewSource={this.handleViewSource}
|
||||
onSelect={this.handleSelect}
|
||||
onHighlight={this.handleHighlight}
|
||||
onToggleCardstack={() => this.props.actions.updateSelected([])}
|
||||
getNarrativeLinks={event => this.getNarrativeLinks(event)}
|
||||
getCategoryColor={category => this.getCategoryColor(category)}
|
||||
/>
|
||||
<Timeline
|
||||
methods={{
|
||||
onSelect: this.handleSelect,
|
||||
onUpdateTimerange: this.updateTimerange,
|
||||
getCategoryColor: category => this.getCategoryColor(category)
|
||||
}}
|
||||
/>
|
||||
<InfoPopUp
|
||||
ui={this.props.ui}
|
||||
app={this.props.app}
|
||||
toggle={() => this.props.actions.toggleInfoPopup()}
|
||||
/>
|
||||
<NarrativeCard
|
||||
onSelect={this.handleSelect}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
<NarrativeCard
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
<Notification
|
||||
isNotification={this.props.app.flags.isNotification}
|
||||
notifications={this.props.domain.notifications}
|
||||
onToggle={this.props.actions.markNotificationsRead}
|
||||
/>
|
||||
<MediaOverlay
|
||||
|
||||
/>
|
||||
<LoadingOverlay
|
||||
ui={this.props.app.flags.isFetchingDomain}
|
||||
language={this.props.app.language}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => state,
|
||||
mapDispatchToProps,
|
||||
)(Dashboard);
|
||||
@@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as actions from '../actions';
|
||||
|
||||
import MediaOverlay from './MediaOverlay.jsx';
|
||||
import LoadingOverlay from './presentational/LoadingOverlay';
|
||||
import Map from './Map.jsx';
|
||||
import Toolbar from './Toolbar.jsx';
|
||||
@@ -48,7 +49,6 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
|
||||
handleViewSource(source) {
|
||||
console.log('handleViewSource: to implement in Dashboard.jsx')
|
||||
this.props.actions.updateSource(source)
|
||||
}
|
||||
|
||||
@@ -131,6 +131,13 @@ class Dashboard extends React.Component {
|
||||
notifications={this.props.domain.notifications}
|
||||
onToggle={this.props.actions.markNotificationsRead}
|
||||
/>
|
||||
{this.props.app.source ? (
|
||||
<MediaOverlay
|
||||
onCancel={() => {
|
||||
this.props.actions.updateSource(null)}
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<LoadingOverlay
|
||||
ui={this.props.app.flags.isFetchingDomain}
|
||||
language={this.props.app.language}
|
||||
|
||||
27
src/components/MediaOverlay.jsx
Normal file
27
src/components/MediaOverlay.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
|
||||
class MediaOverlay extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="mo-overlay">
|
||||
<div className="mo-container" onClick={this.props.onCancel}>
|
||||
<div className="mo-media-container">
|
||||
<iframe
|
||||
className="vimeo-iframe"
|
||||
src="https://player.vimeo.com/video/33044546"
|
||||
frameborder="0"
|
||||
webkitallowfullscreen
|
||||
mozallowfullscreen
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
{/* <div className="mo-controls"> */}
|
||||
{/* ciao ciao */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MediaOverlay
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
UPDATE_TAGFILTERS,
|
||||
UPDATE_TIMERANGE,
|
||||
UPDATE_NARRATIVE,
|
||||
UPDATE_SOURCE,
|
||||
RESET_ALLFILTERS,
|
||||
TOGGLE_LANGUAGE,
|
||||
TOGGLE_MAPVIEW,
|
||||
@@ -117,6 +118,13 @@ function toggleMapView(appState, action) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateSource(appState, action) {
|
||||
return {
|
||||
...appState,
|
||||
source: action.source
|
||||
}
|
||||
}
|
||||
|
||||
function fetchError(state, action) {
|
||||
return {
|
||||
...state,
|
||||
@@ -181,6 +189,8 @@ function app(appState = initial.app, action) {
|
||||
return updateTimeRange(appState, action);
|
||||
case UPDATE_NARRATIVE:
|
||||
return updateNarrative(appState, action);
|
||||
case UPDATE_SOURCE:
|
||||
return updateSource(appState, action);
|
||||
case RESET_ALLFILTERS:
|
||||
return resetAllFilters(appState, action);
|
||||
case TOGGLE_LANGUAGE:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@import 'header';
|
||||
@import 'cardstack';
|
||||
@import 'narrativecard';
|
||||
@import 'mediaoverlay';
|
||||
@import 'map';
|
||||
@import 'timeline';
|
||||
@import 'tag-filters';
|
||||
|
||||
51
src/scss/mediaoverlay.scss
Normal file
51
src/scss/mediaoverlay.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
$vimeo-height: 800px;
|
||||
$vimeo-width: 1000px;
|
||||
|
||||
.mo-overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(230, 230, 230, 0.5);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.mo-container {
|
||||
background-color: transparent;
|
||||
max-width: 80vw;
|
||||
min-width: 80vw;
|
||||
max-height: 90vh;
|
||||
min-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.mo-controls, .mo-media-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mo-media-container {
|
||||
max-height: $vimeo-height;
|
||||
}
|
||||
|
||||
.mo-controls {
|
||||
color: white;
|
||||
width: $vimeo-width;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.vimeo-iframe {
|
||||
min-height: $vimeo-height;
|
||||
max-height: $vimeo-height;
|
||||
min-width: $vimeo-width;
|
||||
max-width: $vimeo-width;
|
||||
border: none;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
const initial = {
|
||||
/*
|
||||
* The Domain or 'domain' of this state refers to the tree of data
|
||||
* available for render and display.
|
||||
* Selections and filters in the 'app' subtree will operate the domain
|
||||
* in mapStateToProps of the Dashboard, and deterimne which items
|
||||
* in the domain will get rendered by React
|
||||
*/
|
||||
* The Domain or 'domain' of this state refers to the tree of data
|
||||
* available for render and display.
|
||||
* Selections and filters in the 'app' subtree will operate the domain
|
||||
* in mapStateToProps of the Dashboard, and deterimne which items
|
||||
* in the domain will get rendered by React
|
||||
*/
|
||||
domain: {
|
||||
events: [],
|
||||
narratives: [],
|
||||
@@ -17,24 +17,25 @@ const initial = {
|
||||
},
|
||||
|
||||
/*
|
||||
* The 'app' subtree of this state determines the data and information to be
|
||||
* displayed.
|
||||
* It may refer to those the user interacts with, by selecting,
|
||||
* fitlering and so on, which ultimately operate on the data to be displayed.
|
||||
* Additionally, some of the 'app' flags are determined by the config file
|
||||
* or by the characteristics of the client, browser, etc.
|
||||
*/
|
||||
* The 'app' subtree of this state determines the data and information to be
|
||||
* displayed.
|
||||
* It may refer to those the user interacts with, by selecting,
|
||||
* fitlering and so on, which ultimately operate on the data to be displayed.
|
||||
* Additionally, some of the 'app' flags are determined by the config file
|
||||
* or by the characteristics of the client, browser, etc.
|
||||
*/
|
||||
app: {
|
||||
errors: {
|
||||
source: null,
|
||||
},
|
||||
highlighted: null,
|
||||
selected: [],
|
||||
source: null,
|
||||
narrative: null,
|
||||
filters: {
|
||||
timerange: [
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"),
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00")
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"),
|
||||
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00")
|
||||
],
|
||||
tags: [],
|
||||
categories: [],
|
||||
@@ -54,36 +55,36 @@ const initial = {
|
||||
duration: 1576800,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '3 meses',
|
||||
duration: 129600,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '3 días',
|
||||
duration: 4320,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '12 horas',
|
||||
duration: 720,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '2 horas',
|
||||
duration: 120,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '30 min',
|
||||
duration: 30,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '10 min',
|
||||
duration: 10,
|
||||
active: false
|
||||
}],
|
||||
{
|
||||
label: '3 meses',
|
||||
duration: 129600,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '3 días',
|
||||
duration: 4320,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '12 horas',
|
||||
duration: 720,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '2 horas',
|
||||
duration: 120,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '30 min',
|
||||
duration: 30,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
label: '10 min',
|
||||
duration: 10,
|
||||
active: false
|
||||
}],
|
||||
features: {
|
||||
USE_TAGS: process.env.features.USE_TAGS,
|
||||
USE_SEARCH: process.env.features.USE_SEARCH
|
||||
@@ -99,10 +100,10 @@ const initial = {
|
||||
},
|
||||
|
||||
/*
|
||||
* The 'ui' subtree of this state refers the state of the cosmetic
|
||||
* elements of the application, such as color palettes of categories
|
||||
* as well as dom elements to attach SVG
|
||||
*/
|
||||
* The 'ui' subtree of this state refers the state of the cosmetic
|
||||
* elements of the application, such as color palettes of categories
|
||||
* as well as dom elements to attach SVG
|
||||
*/
|
||||
ui: {
|
||||
style: {
|
||||
categories: {
|
||||
|
||||
Reference in New Issue
Block a user