mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 21:38:35 +03:00
all tags->filters
This commit is contained in:
@@ -4,7 +4,7 @@ import React from 'react'
|
||||
import CardTime from './presentational/Card/Time'
|
||||
import CardLocation from './presentational/Card/Location'
|
||||
import CardCaret from './presentational/Card/Caret'
|
||||
import CardTags from './presentational/Card/Tags'
|
||||
import CardFilters from './presentational/Card/Filters'
|
||||
import CardSummary from './presentational/Card/Summary'
|
||||
import CardSource from './presentational/Card/Source'
|
||||
import CardNarrative from './presentational/Card/Narrative'
|
||||
@@ -38,13 +38,13 @@ class Card extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderTags () {
|
||||
if (!this.props.tags || (this.props.tags && this.props.tags.length === 0)) {
|
||||
renderFilters () {
|
||||
if (!this.props.filters || (this.props.filters && this.props.filters.length === 0)) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<CardTags
|
||||
tags={this.props.tags || []}
|
||||
<CardFilters
|
||||
filters={this.props.filters || []}
|
||||
language={this.props.language}
|
||||
/>
|
||||
)
|
||||
@@ -137,7 +137,7 @@ class Card extends React.Component {
|
||||
renderExtra () {
|
||||
return (
|
||||
<div className='card-bottomhalf'>
|
||||
{this.renderTags()}
|
||||
{this.renderFilters()}
|
||||
{this.renderSources()}
|
||||
{this.renderNarrative()}
|
||||
</div>
|
||||
|
||||
@@ -104,7 +104,7 @@ class Dashboard extends React.Component {
|
||||
setNarrative (narrative) {
|
||||
// only handleSelect if narrative is not null
|
||||
if (narrative) {
|
||||
this.props.actions.clearFilter('tags')
|
||||
this.props.actions.clearFilter('filters')
|
||||
this.props.actions.clearFilter('categories')
|
||||
this.handleSelect([ narrative.steps[0] ])
|
||||
}
|
||||
@@ -152,7 +152,7 @@ class Dashboard extends React.Component {
|
||||
isNarrative={!!app.narrative}
|
||||
methods={{
|
||||
onTitle: actions.toggleCover,
|
||||
onTagFilter: tag => actions.toggleFilter('tags', tag),
|
||||
onSelectFilter: filter => actions.toggleFilter('filters', filter),
|
||||
onCategoryFilter: category => actions.toggleFilter('categories', category),
|
||||
onSelectNarrative: this.setNarrative
|
||||
}}
|
||||
|
||||
@@ -205,6 +205,7 @@ class Map extends React.Component {
|
||||
return (
|
||||
<Events
|
||||
svg={this.svgRef.current}
|
||||
events={this.props.domain.events}
|
||||
locations={this.props.domain.locations}
|
||||
styleLocation={this.styleLocation}
|
||||
categories={this.props.domain.categories}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default ({
|
||||
{categories.map(cat => {
|
||||
return (<li
|
||||
key={cat.category.replace(/ /g, '_')}
|
||||
className={'tag-filter active'}
|
||||
className={'filter-filter active'}
|
||||
style={{ marginLeft: '20px' }}
|
||||
>
|
||||
<Checkbox
|
||||
|
||||
@@ -10,10 +10,10 @@ function allAssociatedKeys (node) {
|
||||
return childKeys
|
||||
}
|
||||
|
||||
function TagListPanel ({
|
||||
tags,
|
||||
activeTags,
|
||||
onTagFilter,
|
||||
function FilterListPanel ({
|
||||
filters,
|
||||
activeFilters,
|
||||
onSelectFilter,
|
||||
language
|
||||
}) {
|
||||
function createNodeComponent (node, depth) {
|
||||
@@ -22,21 +22,21 @@ function TagListPanel ({
|
||||
return (
|
||||
<li
|
||||
key={node.key.replace(/ /g, '_')}
|
||||
className={'tag-filter'}
|
||||
className={'filter-filter'}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
>
|
||||
{/* <svg width='10' height='10'> */}
|
||||
{/* <g className='tag-inline'> */}
|
||||
{/* <g className='filter-inline'> */}
|
||||
{/* <path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(270)' /> */}
|
||||
{/* </g> */}
|
||||
{/* </svg> */}
|
||||
<Checkbox
|
||||
label={node.key}
|
||||
isActive={activeTags.includes(node.key)}
|
||||
onClickCheckbox={() => onTagFilter(matchingKeys)}
|
||||
isActive={activeFilters.includes(node.key)}
|
||||
onClickCheckbox={() => onSelectFilter(matchingKeys)}
|
||||
/>
|
||||
{children.length > 0
|
||||
? children.map(tag => createNodeComponent(tag, depth + 1))
|
||||
? children.map(filter => createNodeComponent(filter, depth + 1))
|
||||
: null}
|
||||
</li>
|
||||
)
|
||||
@@ -45,18 +45,18 @@ function TagListPanel ({
|
||||
function renderTree (children) {
|
||||
return (
|
||||
<div>
|
||||
{Object.values(children).map(tag => createNodeComponent(tag, 1))}
|
||||
{Object.values(children).map(filter => createNodeComponent(filter, 1))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='react-innertabpanel'>
|
||||
<h2>{copy[language].toolbar.tags}</h2>
|
||||
<p>{copy[language].toolbar.explore_by_tag__description}</p>
|
||||
{renderTree(tags.children)}
|
||||
<h2>{copy[language].toolbar.filters}</h2>
|
||||
<p>{copy[language].toolbar.explore_by_filter__description}</p>
|
||||
{renderTree(filters.children)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagListPanel
|
||||
export default FilterListPanel
|
||||
@@ -6,7 +6,7 @@ import * as selectors from '../../selectors'
|
||||
|
||||
import { Tabs, TabPanel } from 'react-tabs'
|
||||
import Search from './Search'
|
||||
import TagListPanel from './TagListPanel'
|
||||
import FilterListPanel from './FilterListPanel'
|
||||
import CategoriesListPanel from './CategoriesListPanel'
|
||||
import BottomActions from './BottomActions'
|
||||
import copy from '../../common/data/copy.json'
|
||||
@@ -37,9 +37,9 @@ class Toolbar extends React.Component {
|
||||
<TabPanel>
|
||||
<Search
|
||||
language={this.props.language}
|
||||
tags={this.props.tags}
|
||||
filters={this.props.filters}
|
||||
categories={this.props.categories}
|
||||
tagFilters={this.props.tagFilters}
|
||||
filterFilters={this.props.filterFilters}
|
||||
categoryFilters={this.props.categoryFilters}
|
||||
filter={this.props.filter}
|
||||
/>
|
||||
@@ -73,7 +73,7 @@ class Toolbar extends React.Component {
|
||||
}
|
||||
|
||||
renderToolbarCategoriesPanel () {
|
||||
if (this.props.features.CATEGORIES_AS_TAGS) {
|
||||
if (this.props.features.CATEGORIES_AS_FILTERS) {
|
||||
return (
|
||||
<TabPanel>
|
||||
<CategoriesListPanel
|
||||
@@ -90,10 +90,10 @@ class Toolbar extends React.Component {
|
||||
renderToolbarFilterPanel () {
|
||||
return (
|
||||
<TabPanel>
|
||||
<TagListPanel
|
||||
tags={this.props.tags}
|
||||
activeTags={this.props.activeTags}
|
||||
onTagFilter={this.props.methods.onTagFilter}
|
||||
<FilterListPanel
|
||||
filters={this.props.filters}
|
||||
activeFilters={this.props.activeFilters}
|
||||
onSelectFilter={this.props.methods.onSelectFilter}
|
||||
language={this.props.language}
|
||||
/>
|
||||
</TabPanel>
|
||||
@@ -120,7 +120,7 @@ class Toolbar extends React.Component {
|
||||
{this.renderClosePanel()}
|
||||
<Tabs selectedIndex={this.state._selected}>
|
||||
{features.USE_NARRATIVES ? this.renderToolbarNarrativePanel() : null}
|
||||
{features.CATEGORIES_AS_TAGS ? this.renderToolbarCategoriesPanel() : null}
|
||||
{features.CATEGORIES_AS_FILTERS ? this.renderToolbarCategoriesPanel() : null}
|
||||
{features.USE_FILTERS ? this.renderToolbarFilterPanel() : null}
|
||||
</Tabs>
|
||||
</div>
|
||||
@@ -149,7 +149,7 @@ class Toolbar extends React.Component {
|
||||
let title = copy[this.props.language].toolbar.title
|
||||
if (process.env.display_title) title = process.env.display_title
|
||||
const narrativesLabel = copy[this.props.language].toolbar.narratives_label
|
||||
const tagsLabel = copy[this.props.language].toolbar.tags_label
|
||||
const filtersLabel = copy[this.props.language].toolbar.filters_label
|
||||
const categoriesLabel = 'Categories' // TODO:
|
||||
|
||||
return (
|
||||
@@ -157,8 +157,8 @@ class Toolbar extends React.Component {
|
||||
<div className='toolbar-header'onClick={this.props.methods.onTitle}><p>{title}</p></div>
|
||||
<div className='toolbar-tabs'>
|
||||
{features.USE_NARRATIVES ? this.renderToolbarTab(0, narrativesLabel, 'timeline') : null}
|
||||
{features.CATEGORIES_AS_TAGS ? this.renderToolbarTab(1, categoriesLabel, 'widgets') : null}
|
||||
{features.USE_FILTERS ? this.renderToolbarTab(2, tagsLabel, 'filter_list') : null}
|
||||
{features.CATEGORIES_AS_FILTERS ? this.renderToolbarTab(1, categoriesLabel, 'widgets') : null}
|
||||
{features.USE_FILTERS ? this.renderToolbarTab(features.CATEGORIES_AS_FILTERS ? 2 : 1, filtersLabel, 'filter_list') : null}
|
||||
</div>
|
||||
<BottomActions
|
||||
info={{
|
||||
@@ -192,11 +192,11 @@ class Toolbar extends React.Component {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
tags: selectors.getTagTree(state),
|
||||
filters: selectors.getFilterTree(state),
|
||||
categories: selectors.getCategories(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
language: state.app.language,
|
||||
activeTags: selectors.getActiveTags(state),
|
||||
activeFilters: selectors.getActiveFilters(state),
|
||||
activeCategories: selectors.getActiveCategories(state),
|
||||
viewFilters: state.app.filters.views,
|
||||
narrative: state.app.narrative,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global fetch */
|
||||
import React from 'react'
|
||||
import copy from '../../common/data/copy.json'
|
||||
import TagFilter from './TagFilter'
|
||||
import SelectFilter from './SelectFilter'
|
||||
|
||||
export default class Search extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -21,7 +21,7 @@ export default class Search extends React.Component {
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
this.setState({
|
||||
searchResults: json.tags
|
||||
searchResults: json.filters
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -32,16 +32,16 @@ export default class Search extends React.Component {
|
||||
|
||||
renderSearchResults () {
|
||||
return (
|
||||
this.state.searchResults.map(tag => {
|
||||
this.state.searchResults.map(filter => {
|
||||
return (
|
||||
<TagFilter
|
||||
<SelectFilter
|
||||
isShowTree
|
||||
tags={this.props.tags}
|
||||
filters={this.props.filters}
|
||||
categories={this.props.categories}
|
||||
tagFilters={this.props.tagFilters}
|
||||
filterFilters={this.props.filterFilters}
|
||||
categoryFilters={this.props.categoryFilters}
|
||||
filter={this.props.filter}
|
||||
tag={tag}
|
||||
filter={filter}
|
||||
isCategory={this.props.isCategory}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React from 'react'
|
||||
import Checkbox from '../presentational/Checkbox'
|
||||
|
||||
function TagFilter (props) {
|
||||
function SelectFilter (props) {
|
||||
function isActive () {
|
||||
if (props.isCategory) {
|
||||
return props.categoryFilters.includes(props.tag.id)
|
||||
return props.categoryFilters.includes(props.filter.id)
|
||||
}
|
||||
return props.tagFilters.includes(props.tag.id)
|
||||
return props.filterFilters.includes(props.filter.id)
|
||||
}
|
||||
|
||||
function onClickTag () {
|
||||
function onClickFilter () {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
tags: props.tagFilters.filter(element => element !== props.tag.id)
|
||||
filters: props.filterFilters.filter(element => element !== props.filter.id)
|
||||
})
|
||||
} else {
|
||||
props.filter({
|
||||
tags: props.tagFilters.concat(props.tag.id)
|
||||
filters: props.filterFilters.concat(props.filter.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -24,44 +24,44 @@ function TagFilter (props) {
|
||||
function onClickCategory () {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.filter(element => element !== props.tag.id)
|
||||
categories: props.categoryFilters.filter(element => element !== props.filter.id)
|
||||
})
|
||||
} else {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.concat(props.tag.id)
|
||||
categories: props.categoryFilters.concat(props.filter.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function renderTag () {
|
||||
const tag = props.tag
|
||||
let classes = (isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
let label = `${tag.name} ( ${tag.mentions} )`
|
||||
function renderFilter () {
|
||||
const filter = props.filter
|
||||
let classes = (isActive()) ? 'filter-filter active' : 'filter-filter'
|
||||
let label = `${filter.name} ( ${filter.mentions} )`
|
||||
if (props.isShowTree) {
|
||||
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`
|
||||
label = `${filter.group} > ${filter.subgroup} > ${filter.name} ( ${filter.mentions} )`
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={props.tag.id}
|
||||
key={props.filter.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
isActive={isActive()}
|
||||
label={label}
|
||||
onClickCheckbox={() => onClickTag()}
|
||||
onClickCheckbox={() => onClickFilter()}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function renderCategory () {
|
||||
const category = props.categories[props.tag.id]
|
||||
let classes = (isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
const category = props.categories[props.filter.id]
|
||||
let classes = (isActive()) ? 'filter-filter active' : 'filter-filter'
|
||||
|
||||
if (category) {
|
||||
return (
|
||||
<li
|
||||
key={props.tag.id}
|
||||
key={props.filter.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
@@ -76,7 +76,7 @@ function TagFilter (props) {
|
||||
}
|
||||
|
||||
if (props.isCategory) return (renderCategory())
|
||||
return (renderTag())
|
||||
return (renderFilter())
|
||||
}
|
||||
|
||||
export default TagFilter
|
||||
export default SelectFilter
|
||||
36
src/components/presentational/Card/Filters.js
Normal file
36
src/components/presentational/Card/Filters.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../../common/data/copy.json'
|
||||
|
||||
const CardFilters = ({ filters, language }) => {
|
||||
const filtersLang = copy[language].cardstack.filters
|
||||
const noFiltersLang = copy[language].cardstack.nofilters
|
||||
|
||||
if (filters.length > 0) {
|
||||
return (
|
||||
<div className='card-row card-cell filters'>
|
||||
<h4>{filtersLang}:</h4>
|
||||
<p>
|
||||
{filters.map((filter, idx) => {
|
||||
return (
|
||||
<span className='filter'>
|
||||
<small>{filter.name}</small>
|
||||
{(idx < filters.length - 1)
|
||||
? ','
|
||||
: ''}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='card-row card-cell filters'>
|
||||
<h4>{filtersLang}</h4>
|
||||
<p><small>{noFiltersLang}</small></p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardFilters
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import copy from '../../../common/data/copy.json'
|
||||
|
||||
const CardTags = ({ tags, language }) => {
|
||||
const tagsLang = copy[language].cardstack.tags
|
||||
const noTagsLang = copy[language].cardstack.notags
|
||||
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className='card-row card-cell tags'>
|
||||
<h4>{tagsLang}:</h4>
|
||||
<p>
|
||||
{tags.map((tag, idx) => {
|
||||
return (
|
||||
<span className='tag'>
|
||||
<small>{tag.name}</small>
|
||||
{(idx < tags.length - 1)
|
||||
? ','
|
||||
: ''}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='card-row card-cell tags'>
|
||||
<h4>{tagsLang}</h4>
|
||||
<p><small>{noTagsLang}</small></p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardTags
|
||||
@@ -3,7 +3,17 @@ import { Portal } from 'react-portal'
|
||||
import colors from '../../../common/global.js'
|
||||
import { calcOpacity } from '../../../common/utilities'
|
||||
|
||||
function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, selected, narrative, onSelect, svg, locations }) {
|
||||
function MapEvents ({
|
||||
getCategoryColor,
|
||||
categories,
|
||||
projectPoint,
|
||||
styleLocation,
|
||||
selected,
|
||||
narrative,
|
||||
onSelect,
|
||||
svg,
|
||||
locations
|
||||
}) {
|
||||
function getCoordinatesForPercent (radius, percent) {
|
||||
const x = radius * Math.cos(2 * Math.PI * percent)
|
||||
const y = radius * Math.sin(2 * Math.PI * percent)
|
||||
@@ -38,7 +48,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
...extraStyles
|
||||
})
|
||||
|
||||
const colorSlices = location.events.map(e => getCategoryColor(e.category))
|
||||
const colorSlices = location.events.map(e => e.colour ? e.colour : getCategoryColor(e.category))
|
||||
|
||||
let cumulativeAngleSweep = 0
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ const TimelineEvents = ({
|
||||
y: eventY,
|
||||
onSelect: () => onSelect(event),
|
||||
dims,
|
||||
highlights: features.HIGHLIGHT_GROUPS ? getHighlights(event.tags[features.HIGHLIGHT_GROUPS.tagIndexIndicatingGroup]) : [],
|
||||
highlights: features.HIGHLIGHT_GROUPS ? getHighlights(event.filters[features.HIGHLIGHT_GROUPS.filterIndexIndicatingGroup]) : [],
|
||||
features
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user