mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 21:38:35 +03:00
Merge pull request #41 from forensic-architecture/topic/redesign-card
Topic/redesign card
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
<title>TimeMap - Forensic Architecture</title>
|
||||
<link rel="stylesheet" href="https://api.mapbox.com/mapbox.js/v3.1.1/mapbox.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Merriweather" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Merriweather" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -66,7 +66,7 @@ export function fetchDomain () {
|
||||
|
||||
let tagsPromise
|
||||
if (process.env.features.USE_TAGS) {
|
||||
tagsPromise = fetch(TAG_TREE_URL)
|
||||
tagsPromise = fetch(TAG_URL)
|
||||
.then(response => response.json())
|
||||
.catch(handleError('tags'))
|
||||
}
|
||||
@@ -164,14 +164,6 @@ export function updateTagFilters(tag) {
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_NARRATIVE = 'UPDATE_NARRATIVE';
|
||||
export function updateNarrative(narrative) {
|
||||
return {
|
||||
type: UPDATE_NARRATIVE,
|
||||
narrative
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATE_TIMERANGE = 'UPDATE_TIMERANGE';
|
||||
export function updateTimeRange(timerange) {
|
||||
return {
|
||||
@@ -180,6 +172,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 {
|
||||
|
||||
@@ -135,8 +135,11 @@ class Card extends React.Component {
|
||||
renderHeader() {
|
||||
return (
|
||||
<div className="card-collapsed">
|
||||
<div className="card-row">
|
||||
{this.renderTimestamp()}
|
||||
{this.renderLocation()}
|
||||
</div>
|
||||
{this.renderCategory()}
|
||||
{this.renderTimestamp()}
|
||||
{this.renderSummary()}
|
||||
</div>
|
||||
);
|
||||
@@ -150,7 +153,6 @@ class Card extends React.Component {
|
||||
} else {
|
||||
return (
|
||||
<div className="card-bottomhalf">
|
||||
{this.renderLocation()}
|
||||
{this.renderTags()}
|
||||
{this.renderSource()}
|
||||
{this.renderNarrative()}
|
||||
|
||||
@@ -109,6 +109,9 @@ class Dashboard extends React.Component {
|
||||
onSelect={this.handleSelect}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
<NarrativeCard
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
<Notification
|
||||
isNotification={this.props.app.flags.isNotification}
|
||||
notifications={this.props.domain.notifications}
|
||||
|
||||
@@ -113,8 +113,8 @@ class Toolbar extends React.Component {
|
||||
<div className="toolbar-header"><p>{title}</p></div>
|
||||
<div className="toolbar-tabs">
|
||||
{/*this.renderToolbarTab(0, 'search')*/}
|
||||
{this.renderToolbarTab(0, 'Narratives')}
|
||||
{(isTags) ? this.renderToolbarTab(1, 'Explore by tag') : ''}
|
||||
{this.renderToolbarTab(0, 'Focus stories')}
|
||||
{this.renderToolbarTab(1, 'Explore freely')}
|
||||
</div>
|
||||
<ToolbarBottomActions
|
||||
actions={this.props.actions}
|
||||
@@ -137,6 +137,42 @@ class Toolbar extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderToolbarNavs() {
|
||||
if (this.props.narratives) {
|
||||
return this.props.narratives.map((nar, idx) => {
|
||||
const isActive = (idx === this.state.tab);
|
||||
|
||||
let classes = (isActive) ? 'toolbar-tab active' : 'toolbar-tab';
|
||||
|
||||
return (
|
||||
<div className={classes} onClick={() => { this.toggleTab(idx); }}>
|
||||
<div className="tab-caption">{nar.label}</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
renderToolbarTabs() {
|
||||
const title = copy[this.props.language].toolbar.title;
|
||||
const isTags = this.props.tags && (this.props.tags.children > 0);
|
||||
|
||||
return (
|
||||
<div className="toolbar">
|
||||
<div className="toolbar-header"><p>{title}</p></div>
|
||||
<div className="toolbar-tabs">
|
||||
{/*this.renderToolbarTab(0, 'search')*/}
|
||||
{this.renderToolbarTab(0, 'Narratives')}
|
||||
{(isTags) ? this.renderToolbarTab(1, 'Explore by tag') : ''}
|
||||
</div>
|
||||
<ToolbarBottomActions
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="toolbar-wrapper" className="toolbar-wrapper">
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { capitalizeFirstLetter } from '../../js/utilities.js';
|
||||
|
||||
const CardCategory = ({ categoryTitle, categoryLabel, color }) => (
|
||||
<div className="event-card-section category">
|
||||
<div className="card-row card-cell category">
|
||||
<h4>{categoryTitle}</h4>
|
||||
<p>
|
||||
{capitalizeFirstLetter(categoryLabel)}
|
||||
<span className='color-category' style={{ background: color }}/>
|
||||
{categoryLabel}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,20 +5,24 @@ import { isNotNullNorUndefined } from '../../js/utilities';
|
||||
|
||||
const CardLocation = ({ language, location }) => {
|
||||
|
||||
const location_lang = copy[language].cardstack.location;
|
||||
if (isNotNullNorUndefined(location)) {
|
||||
return (
|
||||
<p className="event-card-section location">
|
||||
<h4>{location_lang}</h4>
|
||||
<p>{location}</p>
|
||||
</p>
|
||||
<div className="card-cell location">
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
{location}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const unknown = copy[language].cardstack.unknown_location;
|
||||
return (
|
||||
<p className="event-card-section location">
|
||||
<h4>{location_lang}</h4>
|
||||
<p>Sin localización conocida.</p>
|
||||
</p>
|
||||
<div className="card-cell location">
|
||||
<p>
|
||||
<i className="material-icons left">location_on</i>
|
||||
{unknown}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import React from 'react';
|
||||
import CardNarrativeLink from './CardNarrativeLink';
|
||||
|
||||
const CardNarrative = (props) => (
|
||||
<div className="event-card-section">
|
||||
<div className="card-row">
|
||||
<h4>Connected events</h4>
|
||||
<p>Next: <CardNarrativeLink {...props} event={props.next}/></p>
|
||||
<p>Previous: <CardNarrativeLink {...props} event={props.prev} /></p>
|
||||
<div className="card-cell">
|
||||
<p>← <CardNarrativeLink {...props} event={props.next}/></p>
|
||||
<p>→ <CardNarrativeLink {...props} event={props.prev} /></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ const CardNarrativeLink = ({ event, makeTimelabel, select }) => {
|
||||
|
||||
return (
|
||||
<a onClick={() => select(event)}>
|
||||
{`${timelabel} - ${event.location}`}
|
||||
<small>{`${timelabel} / ${event.location}`}</small>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (<a className="disabled">None</a>);
|
||||
return (<a className="disabled"><small>None</small></a>);
|
||||
}
|
||||
|
||||
export default CardNarrativeLink;
|
||||
|
||||
@@ -4,11 +4,12 @@ import copy from '../../js/data/copy.json';
|
||||
|
||||
const CardSource = ({ source, language }) => {
|
||||
const source_lang = copy[language].cardstack.source;
|
||||
if (!source) source = copy[language].cardstack.unknown_source;
|
||||
|
||||
return (
|
||||
<div className="event-card-section source">
|
||||
<h4>{source_lang}</h4>
|
||||
<p>{source}</p>
|
||||
<div className="card-row card-cell source">
|
||||
<h4>{source_lang}: </h4>
|
||||
<p><small>{source}</small></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ const CardSummary = ({ language, description, isHighlighted }) => {
|
||||
const descriptionText = (isHighlighted) ? description : `${description.substring(0, 40)}...`;
|
||||
|
||||
return (
|
||||
<div className="event-card-section summary">
|
||||
<h4>{summary}</h4>
|
||||
<p>{descriptionText}</p>
|
||||
<div className="card-row summary">
|
||||
<div className="card-cell">
|
||||
<h4>{summary}</h4>
|
||||
<p>{descriptionText}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,27 +3,35 @@ import React from 'react';
|
||||
import copy from '../../js/data/copy.json';
|
||||
|
||||
const CardTags = ({ tags, language }) => {
|
||||
const label = copy[language].cardstack.people;
|
||||
const tags_lang = copy[language].cardstack.tags;
|
||||
const no_tags_lang = copy[language].cardstack.notags;
|
||||
|
||||
return (
|
||||
<div className="event-card-section tags">
|
||||
<h4>{label}</h4>
|
||||
<p>{
|
||||
tags.map((tag, idx) => {
|
||||
return (
|
||||
<span className="tag">
|
||||
{tag.name}
|
||||
{
|
||||
(idx < tags.length - 1)
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className="card-row card-cell tags">
|
||||
<h4>{tags_lang}:</h4>
|
||||
<p>
|
||||
{tags.map((tag, idx) => {
|
||||
return (
|
||||
<span className="tag">
|
||||
<small>{tag.name}</small>
|
||||
{(idx < tags.length - 1)
|
||||
? ','
|
||||
: ''
|
||||
}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}</p>
|
||||
: ''}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="card-row card-cell tags">
|
||||
<h4>{tags_lang}</h4>
|
||||
<p><small>{no_tags_lang}</small></p>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default CardTags;
|
||||
|
||||
@@ -12,16 +12,20 @@ const CardTimestamp = ({ makeTimelabel, language, timestamp }) => {
|
||||
if (isNotNullNorUndefined(timestamp)) {
|
||||
const timelabel = makeTimelabel(timestamp);
|
||||
return (
|
||||
<div className="event-card-section timestamp">
|
||||
<h4>{daytime_lang}</h4>
|
||||
{timelabel}
|
||||
<div className="card-cell timestamp">
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
{timelabel}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="event-card-section timestamp">
|
||||
<h4>{daytime_lang}</h4>
|
||||
{unknown_lang}
|
||||
<div className="card-cell timestamp">
|
||||
<p>
|
||||
<i className="material-icons left">today</i>
|
||||
{unknown_lang}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,10 +133,12 @@
|
||||
"unknown_time": "Unknown time",
|
||||
"location": "Localization",
|
||||
"incident_type": "Type of action",
|
||||
"description": "Summary of facts",
|
||||
"people": "People involved",
|
||||
"description": "Summary",
|
||||
"tags": "Tags",
|
||||
"notags": "No known tags for this event.",
|
||||
"source": "Source",
|
||||
"category": "According to",
|
||||
"unknown_source": "Unknown source. The relability of this event cannot be confirmed.",
|
||||
"category": "Category",
|
||||
"communication": "Communication",
|
||||
"transmitter": "Transmitter",
|
||||
"receiver": "Receiver",
|
||||
|
||||
@@ -36,6 +36,14 @@ export function isNotNullNorUndefined(variable) {
|
||||
return (typeof variable !== 'undefined' && variable !== null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Capitalizes _only_ the first letter of a string
|
||||
* Taken from: https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript
|
||||
*/
|
||||
export function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Date object given a datetime string of the format: "2016-09-10T07:00:00"
|
||||
* @param {string} datetime
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
$event_default: red;
|
||||
|
||||
$offwhite: #efefef;
|
||||
$lightwhite: #dfdfdf;
|
||||
$midwhite: #a0a0a0;
|
||||
$darkwhite: darken($midwhite, 15%);
|
||||
$yellow: #ffd800;
|
||||
|
||||
@@ -10,27 +10,16 @@
|
||||
box-shadow: 0 19px 19px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
|
||||
font-size: $large;
|
||||
line-height: $xxlarge;
|
||||
transition: 0.2s ease;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $yellow;
|
||||
}
|
||||
|
||||
.card-bottomhalf {
|
||||
transition: 0.4s ease;
|
||||
height: auto;
|
||||
|
||||
&.folded {
|
||||
transition: 0.4s ease;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
opacity: 0.9;
|
||||
|
||||
h4 {
|
||||
text-transform: normal;
|
||||
margin-bottom: 0;
|
||||
margin-right: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: $xsmall;
|
||||
color: $darkwhite;
|
||||
font-weight: 100;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
@@ -41,19 +30,57 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.event-card-section {
|
||||
margin-bottom: 10px;
|
||||
.material-icons {
|
||||
font-size: $normal;
|
||||
color: $darkwhite;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid $lightwhite;
|
||||
margin: 5px 0 10px 0;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.card-cell {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h4 {
|
||||
min-width: 80px;
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-cell {
|
||||
a {
|
||||
transition: color 0.2s;
|
||||
}
|
||||
a:hover {
|
||||
color: $red;
|
||||
color: $darkwhite;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
a.disabled {
|
||||
color: $midgrey;
|
||||
font-weight: normal;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card-bottomhalf {
|
||||
transition: 0.4s ease;
|
||||
height: auto;
|
||||
|
||||
&.folded {
|
||||
transition: 0.4s ease;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.card-toggle p {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
@@ -106,28 +133,19 @@
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.color-category {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin: 0px 5px 0 0;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.event-type {
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 0 5px 2px 0;
|
||||
color: $darkgrey;
|
||||
|
||||
&.flagged {
|
||||
background: $red;
|
||||
color: $offwhite;
|
||||
padding: 0 5px;
|
||||
}
|
||||
p {
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,49 +163,4 @@
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
https://github.com/tobiasahlin/SpinKit/blob/master/LICENSE
|
||||
*/
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.double-bounce1, .double-bounce2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: $darkgrey;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
-webkit-animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.double-bounce2 {
|
||||
-webkit-animation-delay: -1.0s;
|
||||
animation-delay: -1.0s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%, 100% { -webkit-transform: scale(0.0) }
|
||||
50% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes sk-bounce {
|
||||
0%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 50% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,50 +31,49 @@
|
||||
opacity: 0;
|
||||
z-index: $hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
https://github.com/tobiasahlin/SpinKit/blob/master/LICENSE
|
||||
*/
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
*/
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
}
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.double-bounce1, .double-bounce2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: $offwhite;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.double-bounce1, .double-bounce2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: $offwhite;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
-webkit-animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
}
|
||||
-webkit-animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
animation: sk-bounce 3.0s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.double-bounce2 {
|
||||
-webkit-animation-delay: -1.0s;
|
||||
animation-delay: -1.0s;
|
||||
}
|
||||
.double-bounce2 {
|
||||
-webkit-animation-delay: -1.0s;
|
||||
animation-delay: -1.0s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%, 100% { -webkit-transform: scale(0.0) }
|
||||
50% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%, 100% { -webkit-transform: scale(0.0) }
|
||||
50% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes sk-bounce {
|
||||
0%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 50% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
@keyframes sk-bounce {
|
||||
0%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 50% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
&:first-child {
|
||||
text-transform: none;
|
||||
font-size: $normal;
|
||||
letter-spacing: 0.05;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ const initial = {
|
||||
categories: {
|
||||
default: 'red',
|
||||
// Add here other categories to differentiate by color, like:
|
||||
alpha: '#c73e1d',
|
||||
beta: '#f40000',
|
||||
alpha: '#00ff00',
|
||||
beta: '#ff0000',
|
||||
other: '#f3de2c'
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user