mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 05:18:34 +03:00
clean tag representation and filtering
This commit is contained in:
@@ -120,7 +120,7 @@ class Dashboard extends React.Component {
|
||||
<Toolbar
|
||||
isNarrative={!!app.narrative}
|
||||
methods={{
|
||||
onTagFilter: actions.updateTagFilters,
|
||||
onTagFilter: actions.toggleTagFilter,
|
||||
onCategoryFilter: actions.updateCategoryFilters,
|
||||
onSelectNarrative: this.setNarrative
|
||||
}}
|
||||
|
||||
@@ -203,7 +203,7 @@ class Map extends React.Component {
|
||||
return (
|
||||
<Events
|
||||
svg={this.svgRef.current}
|
||||
locations={this.props.domain.locations}
|
||||
locations={this.props.domain.visibleLocations}
|
||||
styleLocation={this.styleLocation}
|
||||
categories={this.props.domain.categories}
|
||||
projectPoint={this.projectPoint}
|
||||
@@ -245,7 +245,7 @@ class Map extends React.Component {
|
||||
{this.renderShapes()}
|
||||
{this.renderNarratives()}
|
||||
{this.renderEvents()}
|
||||
{this.renderSelected()}
|
||||
{this.renderSelected()}
|
||||
</React.Fragment>
|
||||
) : null
|
||||
|
||||
@@ -261,7 +261,7 @@ class Map extends React.Component {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
domain: {
|
||||
locations: selectors.selectLocations(state),
|
||||
visibleLocations: selectors.selectVisibleLocations(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
categories: selectors.selectCategories(state),
|
||||
sites: selectors.getSites(state),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global fetch */
|
||||
import React from 'react'
|
||||
import copy from '../js/data/copy.json'
|
||||
import TagFilter from './TagFilter.jsx'
|
||||
import TagFilter from './TagFilter'
|
||||
|
||||
export default class Search extends React.Component {
|
||||
constructor (props) {
|
||||
|
||||
82
src/components/TagFilter.js
Normal file
82
src/components/TagFilter.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
|
||||
function TagFilter (props) {
|
||||
function isActive () {
|
||||
if (props.isCategory) {
|
||||
return props.categoryFilters.includes(props.tag.id)
|
||||
}
|
||||
return props.tagFilters.includes(props.tag.id)
|
||||
}
|
||||
|
||||
function onClickTag () {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
tags: props.tagFilters.filter(element => element !== props.tag.id)
|
||||
})
|
||||
} else {
|
||||
props.filter({
|
||||
tags: props.tagFilters.concat(props.tag.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onClickCategory () {
|
||||
if (isActive()) {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.filter(element => element !== props.tag.id)
|
||||
})
|
||||
} else {
|
||||
props.filter({
|
||||
categories: props.categoryFilters.concat(props.tag.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function renderTag () {
|
||||
const tag = props.tag
|
||||
let classes = (isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
let label = `${tag.name} ( ${tag.mentions} )`
|
||||
if (props.isShowTree) {
|
||||
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={props.tag.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
isActive={isActive()}
|
||||
label={label}
|
||||
onClickCheckbox={() => onClickTag()}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function renderCategory () {
|
||||
const category = props.categories[props.tag.id]
|
||||
let classes = (isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
|
||||
if (category) {
|
||||
return (
|
||||
<li
|
||||
key={props.tag.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
isActive={isActive()}
|
||||
label={`${category.name} ( ${category.counts} )`}
|
||||
onClickCheckbox={onClickCategory}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (<div />)
|
||||
}
|
||||
|
||||
if (props.isCategory) return (renderCategory())
|
||||
return (renderTag())
|
||||
}
|
||||
|
||||
export default TagFilter
|
||||
@@ -1,84 +0,0 @@
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
|
||||
class TagFilter extends React.Component {
|
||||
isActive () {
|
||||
if (this.props.isCategory) {
|
||||
return this.props.categoryFilters.includes(this.props.tag.id)
|
||||
}
|
||||
return this.props.tagFilters.includes(this.props.tag.id)
|
||||
}
|
||||
|
||||
onClickTag () {
|
||||
if (this.isActive()) {
|
||||
this.props.filter({
|
||||
tags: this.props.tagFilters.filter(element => element !== this.props.tag.id)
|
||||
})
|
||||
} else {
|
||||
this.props.filter({
|
||||
tags: this.props.tagFilters.concat(this.props.tag.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onClickCategory () {
|
||||
if (this.isActive()) {
|
||||
this.props.filter({
|
||||
categories: this.props.categoryFilters.filter(element => element !== this.props.tag.id)
|
||||
})
|
||||
} else {
|
||||
this.props.filter({
|
||||
categories: this.props.categoryFilters.concat(this.props.tag.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
renderTag () {
|
||||
const tag = this.props.tag
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
let label = `${tag.name} ( ${tag.mentions} )`
|
||||
if (this.props.isShowTree) {
|
||||
label = `${tag.group} > ${tag.subgroup} > ${tag.name} ( ${tag.mentions} )`
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={this.props.tag.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
isActive={this.isActive()}
|
||||
label={label}
|
||||
onClickCheckbox={() => this.onClickTag()}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
renderCategory () {
|
||||
const category = this.props.categories[this.props.tag.id]
|
||||
let classes = (this.isActive()) ? 'tag-filter active' : 'tag-filter'
|
||||
|
||||
if (category) {
|
||||
return (
|
||||
<li
|
||||
key={this.props.tag.id}
|
||||
className={classes}
|
||||
>
|
||||
<Checkbox
|
||||
isActive={this.isActive()}
|
||||
label={`${category.name} ( ${category.counts} )`}
|
||||
onClickCheckbox={() => this.onClickCategory()}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (<div />)
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.props.isCategory) return (this.renderCategory())
|
||||
return (this.renderTag())
|
||||
}
|
||||
}
|
||||
|
||||
export default TagFilter
|
||||
45
src/components/TagListPanel.js
Normal file
45
src/components/TagListPanel.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
import copy from '../js/data/copy.json'
|
||||
|
||||
function TagListPanel ({
|
||||
tags,
|
||||
tagFilters,
|
||||
onTagFilter,
|
||||
language
|
||||
}) {
|
||||
function createNodeComponent (node, depth) {
|
||||
return (
|
||||
<li
|
||||
key={node.key.replace(/ /g, '_')}
|
||||
className={'tag-filter active'}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
>
|
||||
<Checkbox
|
||||
label={node.key}
|
||||
isActive={tagFilters.includes(node.key)}
|
||||
onClickCheckbox={() => onTagFilter(node.key)}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function renderTree () {
|
||||
/* NOTE: only render first layer of tags */
|
||||
return (
|
||||
<div>
|
||||
{Object.values(tags.children).map(tag => createNodeComponent(tag, 1))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='react-innertabpanel'>
|
||||
<h2>{copy[language].toolbar.tags}</h2>
|
||||
<p>{copy[language].toolbar.explore_by_tag__description}</p>
|
||||
{renderTree()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagListPanel
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from 'react'
|
||||
import Checkbox from './presentational/Checkbox'
|
||||
import copy from '../js/data/copy.json'
|
||||
|
||||
class TagListPanel extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
treeComponents: []
|
||||
}
|
||||
this.treeComponents = []
|
||||
this.newTagFilters = []
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.computeTree(this.props.tags)// .children[this.props.tagType]);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.computeTree(nextProps.tags)// .children[nextProps.tagType]);
|
||||
}
|
||||
|
||||
onClickCheckbox (obj, type) {
|
||||
obj.active = !obj.active
|
||||
this.props.onTagFilter(obj)
|
||||
}
|
||||
|
||||
createNodeComponent (node, depth) {
|
||||
return (
|
||||
<li
|
||||
key={node.key.replace(/ /g, '_')}
|
||||
className={'tag-filter active'}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
>
|
||||
<Checkbox
|
||||
label={node.key}
|
||||
isActive={node.active}
|
||||
onClickCheckbox={() => this.onClickCheckbox(node, 'tag')}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
traverseNodeAndCreateComponent (node, depth) {
|
||||
// add and create node component
|
||||
const newComponent = this.createNodeComponent(node, depth)
|
||||
this.treeComponents.push(newComponent)
|
||||
depth = depth + 1
|
||||
if (Object.keys(node.children).length > 0) {
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
this.traverseNodeAndCreateComponent(childNode, depth)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
computeTree (node) {
|
||||
this.treeComponents = []
|
||||
let depth = 0
|
||||
this.traverseNodeAndCreateComponent(node, depth)
|
||||
this.setState({ treeComponents: this.treeComponents })
|
||||
}
|
||||
|
||||
renderTree () {
|
||||
return (
|
||||
<div>
|
||||
{this.state.treeComponents.map(c => c)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='react-innertabpanel'>
|
||||
<h2>{copy[this.props.language].toolbar.tags}</h2>
|
||||
<p>{copy[this.props.language].toolbar.explore_by_tag__description}</p>
|
||||
{this.renderTree()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TagListPanel
|
||||
@@ -6,7 +6,7 @@ import * as selectors from '../selectors'
|
||||
|
||||
import { Tabs, TabPanel } from 'react-tabs'
|
||||
import Search from './Search.jsx'
|
||||
import TagListPanel from './TagListPanel.jsx'
|
||||
import TagListPanel from './TagListPanel'
|
||||
import CategoriesListPanel from './CategoriesListPanel.jsx'
|
||||
import ToolbarBottomActions from './ToolbarBottomActions.jsx'
|
||||
import copy from '../js/data/copy.json'
|
||||
@@ -199,7 +199,7 @@ function mapStateToProps (state) {
|
||||
categories: selectors.getCategories(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
language: state.app.language,
|
||||
tagFilters: selectors.selectTagList(state),
|
||||
tagFilters: selectors.getTagsFilter(state),
|
||||
categoryFilters: selectors.selectCategories(state),
|
||||
viewFilters: state.app.filters.views,
|
||||
features: state.app.features,
|
||||
|
||||
@@ -80,6 +80,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
|
||||
const { x, y } = projectPoint([location.latitude, location.longitude])
|
||||
|
||||
// in narrative mode, only render events in narrative
|
||||
// TODO: move this to a selector
|
||||
if (narrative) {
|
||||
const { steps } = narrative
|
||||
const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id)
|
||||
|
||||
Reference in New Issue
Block a user