mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 21:38:35 +03:00
Refactored filter list for display; converting filter paths to node, child objects that are toggleable
This commit is contained in:
2
src/common/constants.js
Normal file
2
src/common/constants.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const FILTER_MODE = 'FILTER'
|
||||
export const NARRATIVE_MODE = 'NARRATIVE'
|
||||
@@ -3,57 +3,73 @@ import Checkbox from '../presentational/Checkbox'
|
||||
import copy from '../../common/data/copy.json'
|
||||
|
||||
/** recursively get an array of node keys to toggle */
|
||||
function childrenToToggle (node, activeFilters, parentOn) {
|
||||
const isOn = activeFilters.includes(node.key)
|
||||
if (!node.children) {
|
||||
return [node.key]
|
||||
function childrenToToggle (filter, activeFilters, parentOn) {
|
||||
const [key, children] = filter
|
||||
const isOn = activeFilters.includes(key)
|
||||
if (children === {}) {
|
||||
return [key]
|
||||
}
|
||||
const childKeys = Object.values(node.children)
|
||||
.flatMap(n => childrenToToggle(n, activeFilters, isOn))
|
||||
const childKeys = Object.entries(children)
|
||||
.flatMap(filter => childrenToToggle(filter, activeFilters, isOn))
|
||||
// NB: if turning a parent off, don't toggle off children on.
|
||||
// likewise if turning a parent on, don't toggle on children off
|
||||
if (!((!parentOn && isOn) || (parentOn && !isOn))) {
|
||||
childKeys.push(node.key)
|
||||
childKeys.push(key)
|
||||
}
|
||||
return childKeys
|
||||
}
|
||||
|
||||
function aggregatePaths (filters) {
|
||||
const aggregated = {}
|
||||
|
||||
filters.forEach(item => {
|
||||
let currentDepth = aggregated
|
||||
|
||||
item.filter_paths.forEach(path => {
|
||||
if (!(path in aggregated)) {
|
||||
currentDepth[path] = {}
|
||||
}
|
||||
currentDepth = currentDepth[path]
|
||||
})
|
||||
})
|
||||
|
||||
return aggregated
|
||||
}
|
||||
|
||||
function FilterListPanel ({
|
||||
filters,
|
||||
activeFilters,
|
||||
onSelectFilter,
|
||||
language
|
||||
}) {
|
||||
function createNodeComponent (node, depth) {
|
||||
const matchingKeys = childrenToToggle(node, activeFilters, activeFilters.includes(node.key))
|
||||
const children = Object.values(node.children)
|
||||
function createNodeComponent (filter, depth) {
|
||||
const [key, children] = filter
|
||||
const matchingKeys = childrenToToggle(filter, activeFilters, activeFilters.includes(key))
|
||||
|
||||
return (
|
||||
<li
|
||||
key={node.key.replace(/ /g, '_')}
|
||||
key={key.replace(/ /g, '_')}
|
||||
className={'filter-filter'}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
>
|
||||
{/* <svg width='10' height='10'> */}
|
||||
{/* <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={activeFilters.includes(node.key)}
|
||||
label={key}
|
||||
isActive={activeFilters.includes(key)}
|
||||
onClickCheckbox={() => onSelectFilter(matchingKeys)}
|
||||
/>
|
||||
{children.length > 0
|
||||
? children.map(filter => createNodeComponent(filter, depth + 1))
|
||||
{Object.keys(children).length > 0
|
||||
? Object.entries(children).map(filter => createNodeComponent(filter, depth + 1))
|
||||
: null}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function renderTree (children) {
|
||||
function renderTree (filters) {
|
||||
const aggregatedFilterPaths = aggregatePaths(filters)
|
||||
console.info(aggregatedFilterPaths)
|
||||
return (
|
||||
<div>
|
||||
{Object.values(children).map(filter => createNodeComponent(filter, 1))}
|
||||
{Object.entries(aggregatedFilterPaths).map(filter => createNodeComponent(filter, 1))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -62,7 +78,7 @@ function FilterListPanel ({
|
||||
<div className='react-innertabpanel'>
|
||||
<h2>{copy[language].toolbar.filters}</h2>
|
||||
<p>{copy[language].toolbar.explore_by_filter__description}</p>
|
||||
{renderTree(filters.children)}
|
||||
{renderTree(filters)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ class Toolbar extends React.Component {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
filters: selectors.getFilterTree(state),
|
||||
filters: selectors.getFilters(state),
|
||||
categories: selectors.getCategories(state),
|
||||
narratives: selectors.selectNarratives(state),
|
||||
language: state.app.language,
|
||||
|
||||
@@ -114,7 +114,7 @@ function toggleFilter (appState, action) {
|
||||
action.value = [action.value]
|
||||
}
|
||||
|
||||
let newFilters = appState.filters[action.filter].slice(0)
|
||||
let newFilters = appState.associations.filters.slice(0)
|
||||
action.value.forEach(vl => {
|
||||
if (newFilters.includes(vl)) {
|
||||
newFilters = newFilters.filter(s => s !== vl)
|
||||
@@ -125,9 +125,9 @@ function toggleFilter (appState, action) {
|
||||
|
||||
return {
|
||||
...appState,
|
||||
filters: {
|
||||
...appState.filters,
|
||||
[action.filter]: newFilters
|
||||
associations: {
|
||||
...appState.associations,
|
||||
filters: newFilters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createSelector } from 'reselect'
|
||||
import { insetSourceFrom, dateMin, dateMax } from '../common/utilities'
|
||||
import { isTimeRangedIn } from './helpers'
|
||||
import { FILTER_MODE } from '../common/constants'
|
||||
|
||||
// Input selectors
|
||||
export const getEvents = state => state.domain.events
|
||||
@@ -11,8 +12,8 @@ export const getSelected = state => state.app.selected
|
||||
export const getSites = state => state.domain.sites
|
||||
export const getSources = state => state.domain.sources
|
||||
export const getShapes = state => state.domain.shapes
|
||||
export const getFilters = state => state.domain.associations.filter(item => item.mode === FILTER_MODE)
|
||||
export const getNotifications = state => state.domain.notifications
|
||||
export const getFilterTree = state => state.domain.filters
|
||||
export const getActiveFilters = state => state.app.associations.filters
|
||||
export const getActiveCategories = state => state.app.associations.categories
|
||||
export const getTimeRange = state => state.app.timeline.range
|
||||
|
||||
Reference in New Issue
Block a user