Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3a54a4c
Checking in before running aider: In fetchAndLocalizeTypes.fulfilled,…
wbazant Nov 12, 2024
00bba5b
Aider prompt: In fetchAndLocalizeTypes.fulfilled, use currently selec…
wbazant Nov 12, 2024
1c00c12
Aider prompt: Get categories in one useSelector on top
wbazant Nov 13, 2024
6a38d8a
Checking in before running aider: Correctly pass args to categoryChanged
wbazant Nov 13, 2024
537ad9b
Aider prompt: Correctly pass args to categoryChanged
wbazant Nov 13, 2024
3c5c5a6
Checking in before running aider: Add feature to buildSelectTree: ent…
wbazant Nov 13, 2024
397b9a9
Aider prompt: Add feature to buildSelectTree: entries are only visibl…
wbazant Nov 13, 2024
f08304b
Aider prompt: Only show leaf nodes if they match one or more selected…
wbazant Nov 13, 2024
d121b4d
Aider prompt: Add logging to help me debug category logic
wbazant Nov 13, 2024
65fb930
Checking in before running aider: Add 'parentMatchesCategories' and c…
wbazant Nov 13, 2024
2b16287
Aider prompt: Add 'parentMatchesCategories' and copy parentMatchesSea…
wbazant Nov 13, 2024
a283704
Checking in before running aider: Add a fifth 'no category' category,…
wbazant Nov 13, 2024
b022c80
Aider prompt: Add a fifth 'no category' category, which applies only …
wbazant Nov 13, 2024
30abca2
Checking in before running aider: Change how we use categories to dis…
wbazant Nov 13, 2024
0ea6db9
Aider prompt: Add logging that will help me understand what happens w…
wbazant Nov 13, 2024
d22ec94
x
wbazant Nov 13, 2024
f89e1b5
Aider prompt: Move code for matching enabled categories to a helper m…
wbazant Nov 14, 2024
a5124b8
Aider prompt: Add a method to buildSelectTree, getAggregatedCountInEn…
wbazant Nov 14, 2024
d56057e
Checking in before running aider: Display category checkboxes in a 4x…
wbazant Nov 14, 2024
4ca25df
Aider prompt: Display category checkboxes in a 4x2 grid
wbazant Nov 14, 2024
67ff5e5
x
wbazant Nov 14, 2024
c5ff847
x
wbazant Nov 18, 2024
b2415ca
Checking in before running aider: Add a 'isVisible' property to Rende…
wbazant Nov 18, 2024
7c122d8
Aider prompt: Add a 'isVisible' property to RenderTreeNode. Then don'…
wbazant Nov 18, 2024
0cfbe11
x
wbazant Nov 18, 2024
5deeed2
Maybe working now
wbazant Nov 18, 2024
df3a3ce
Checking in before running aider: in fetchMapLocations, filter the ty…
wbazant Nov 18, 2024
c68c344
Checking in before running aider: Since locations/clusters on the map…
wbazant Nov 18, 2024
a85fa04
Aider prompt: Since locations/clusters on the map depend on categorie…
wbazant Nov 18, 2024
1bfc951
Checking in before running aider: There's a bug with categories being…
wbazant Nov 18, 2024
daac47b
Checking in before running aider: Instead of categories as checkboxes…
wbazant Nov 18, 2024
23ddf09
Aider prompt: Instead of categories as checkboxes, do them as a multi…
wbazant Nov 18, 2024
d4189bf
Checking in before running aider: Make sure the selected categories a…
wbazant Nov 18, 2024
fb35acc
Aider prompt: Make sure the selected categories are visible - give th…
wbazant Nov 18, 2024
c456d65
x
wbazant Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/components/filter/CategorySelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styled from 'styled-components/macro'

const TagContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
`

const Tag = styled.div`
display: inline-flex;
align-items: center;
padding: 6px 12px;
background: ${({ theme, selected }) =>
selected ? theme.transparentOrange : ''};
border: 1px solid;
color: ${({ theme, selected }) =>
selected ? theme.secondaryText : theme.text};
border-radius: 16px;
cursor: pointer;
transition: all 0.2s;

&:hover {
background: ${({ theme }) => theme.primaryLight};
color: ${({ theme }) => theme.white};
border-color: ${({ theme }) => theme.primaryLight};
}
`

const CategorySelect = ({ categories, onChange }) => {
const toggleCategory = (category) => {
const newCategories = { ...categories }
newCategories[category] = !newCategories[category]
onChange(newCategories)
}

return (
<TagContainer>
{Object.entries(categories).map(([category, enabled]) => (
<Tag
key={category}
selected={enabled}
onClick={() => toggleCategory(category)}
>
{category === 'noCategory'
? 'No Category'
: category.charAt(0).toUpperCase() + category.slice(1)}
</Tag>
))}
</TagContainer>
)
}

export default CategorySelect
19 changes: 17 additions & 2 deletions src/components/filter/Filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components/macro'

import {
categoryChanged,
invasiveChanged,
muniChanged,
selectionChanged,
} from '../../redux/viewChange'
import buildSelectTree from '../../utils/buildSelectTree'
import Input from '../ui/Input'
import CategorySelect from './CategorySelect'
import FilterButtons from './FilterButtons'
import LabeledCheckbox from './LabeledCheckbox'
import RCTreeSelectSkeleton from './RCTreeSelectSkeleton'
Expand Down Expand Up @@ -58,7 +60,7 @@ const Filter = () => {
)

const dispatch = useDispatch()
const { countsById, types, muni, invasive } = useSelector(
const { countsById, types, muni, invasive, categories } = useSelector(
(state) => state.filter,
)

Expand All @@ -71,15 +73,28 @@ const Filter = () => {
showOnlyOnMap,
searchValue,
types,
Object.entries(categories)
.filter(([_, enabled]) => enabled)
.map(([category]) => category),
),
[typesAccess, countsById, showOnlyOnMap, searchValue, types],
[typesAccess, countsById, showOnlyOnMap, searchValue, types, categories],
)

const { t } = useTranslation()
return (
<>
<div>
<EdibleTypeText>{t('glossary.types')}</EdibleTypeText>
<CategorySelect
categories={categories}
onChange={(newCategories) => {
Object.entries(newCategories).forEach(([category, enabled]) => {
if (categories[category] !== enabled) {
dispatch(categoryChanged(category, enabled))
}
})
}}
/>
<SearchInput
onChange={(e) => setSearchValueDebounced(e.target.value)}
placeholder={t('type')}
Expand Down
98 changes: 51 additions & 47 deletions src/components/filter/TreeSelectView.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,54 +113,58 @@ const TreeSelectView = ({
const isExpanded = Boolean(expandedNodes.has(node.id) | isDisabled)

return (
<React.Fragment key={node.id}>
<TreeNode style={{ paddingLeft: `${level * 1.25}em` }}>
<ControlsContainer>
{node.children.length > 0 ? (
<ToggleButton
onClick={() => handleToggle(node.id)}
node.isVisible && (
<React.Fragment key={node.id}>
<TreeNode style={{ paddingLeft: `${level * 1.25}em` }}>
<ControlsContainer>
{node.children.some((c) => c.isVisible) ? (
<ToggleButton
onClick={() => handleToggle(node.id)}
disabled={isDisabled}
>
<ArrowIcon
style={{
transform: `rotate(${isExpanded ? 90 : 0}deg)`,
transition: 'transform 0.2s',
width: '12px',
height: '12px',
opacity: isDisabled ? 0.5 : 1,
}}
/>
</ToggleButton>
) : (
<ChevronSpace />
)}
<Checkbox
type="checkbox"
checked={node.isSelected}
ref={(el) => {
if (el) {
el.indeterminate = node.isIndeterminate
}
}}
onChange={() => !isDisabled && handleCheckboxChange(node)}
disabled={isDisabled}
>
<ArrowIcon
style={{
transform: `rotate(${isExpanded ? 90 : 0}deg)`,
transition: 'transform 0.2s',
width: '12px',
height: '12px',
opacity: isDisabled ? 0.5 : 1,
}}
/>
</ToggleButton>
) : (
<ChevronSpace />
)}
<Checkbox
type="checkbox"
checked={node.isSelected}
ref={(el) => {
if (el) {
el.indeterminate = node.isIndeterminate
}
}}
onChange={() => !isDisabled && handleCheckboxChange(node)}
disabled={isDisabled}
/>
</ControlsContainer>
<NodeContent>
{node.commonName && (
<CommonName isDisabled={isDisabled}>{node.commonName}</CommonName>
)}
{node.scientificName && (
<ScientificName isDisabled={isDisabled}>
{node.scientificName}
</ScientificName>
)}
<Count>({node.count})</Count>
</NodeContent>
</TreeNode>
{isExpanded &&
node.children.map((child) => renderNode(child, level + 1))}
</React.Fragment>
/>
</ControlsContainer>
<NodeContent>
{node.commonName && (
<CommonName isDisabled={isDisabled}>
{node.commonName}
</CommonName>
)}
{node.scientificName && (
<ScientificName isDisabled={isDisabled}>
{node.scientificName}
</ScientificName>
)}
<Count>({node.count})</Count>
</NodeContent>
</TreeNode>
{isExpanded &&
node.children.map((child) => renderNode(child, level + 1))}
</React.Fragment>
)
)
}

Expand Down
50 changes: 50 additions & 0 deletions src/components/filter/components/filter/CategorySelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled from 'styled-components/macro'

const TagContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
`

const Tag = styled.div`
display: inline-flex;
align-items: center;
padding: 6px 12px;
background: ${({ theme, selected }) =>
selected ? theme.primary : theme.background};
color: ${({ theme, selected }) => (selected ? theme.white : theme.text)};
border: 1px solid ${({ theme }) => theme.border};
border-radius: 16px;
cursor: pointer;
transition: all 0.2s;

&:hover {
background: ${({ theme }) => theme.primaryLight};
color: ${({ theme }) => theme.white};
}
`

const CategorySelect = ({ categories, onChange }) => {
const toggleCategory = (category) => {
const newCategories = { ...categories }
newCategories[category] = !newCategories[category]
onChange(newCategories)
}

return (
<TagContainer>
{Object.entries(categories).map(([category, enabled]) => (
<Tag
key={category}
selected={enabled}
onClick={() => toggleCategory(category)}
>
{category.charAt(0).toUpperCase() + category.slice(1)}
</Tag>
))}
</TagContainer>
)
}

export default CategorySelect
17 changes: 13 additions & 4 deletions src/redux/filterSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ export const filterSlice = createSlice({
invasive: false,
isLoading: false,
countsById: {},
categories: {
forager: true,
freegan: true,
grafter: false,
honeybee: false,
noCategory: false,
},
},
reducers: {
openFilter: (state) => {
Expand All @@ -55,6 +62,10 @@ export const filterSlice = createSlice({
closeFilter: (state) => {
state.isOpenInMobileLayout = false
},
categoryChanged: (state, action) => {
const { category, value } = action.payload
state.categories[category] = value
},
},
extraReducers: {
[fetchFilterCounts.pending]: (state) => {
Expand All @@ -79,13 +90,11 @@ export const filterSlice = createSlice({

[fetchAndLocalizeTypes.fulfilled]: (state, action) => {
const typesAccess = action.payload
state.types = typesAccess
.selectableTypesWithCategories('forager', 'freegan')
.map((t) => t.id)
state.types = typesAccess.selectableTypes().map((t) => t.id)
},
},
})

export const { openFilter, closeFilter } = filterSlice.actions
export const { openFilter, closeFilter, categoryChanged } = filterSlice.actions

export default filterSlice.reducer
33 changes: 29 additions & 4 deletions src/redux/mapSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,30 @@ export const fetchMapLocations = createAsyncThunk(
'map/fetchMapLocations',
async (_, { getState }) => {
const state = getState()
const { types, muni, invasive } = state.filter
const { types, muni, invasive, categories } = state.filter
const { lastMapView } = state.viewport
const { typesAccess } = state.type

if (lastMapView) {
const { bounds, zoom, center: _ } = lastMapView

const filteredTypes = types?.filter((typeId) => {
const type = typesAccess.getType(typeId)
return type.categories.length === 0
? categories.noCategory
: type.categories.some((cat) => categories[cat])
})

return await getLocations(
selectParams(
{ types, muni, invasive, bounds, zoom, center: undefined },
{
types: filteredTypes,
muni,
invasive,
bounds,
zoom,
center: undefined,
},
{ limit: 250 },
),
)
Expand All @@ -32,13 +49,21 @@ export const fetchMapClusters = createAsyncThunk(
'map/fetchMapClusters',
async (_, { getState }) => {
const state = getState()
const { types, muni, invasive } = state.filter
const { types, muni, invasive, categories } = state.filter
const { lastMapView } = state.viewport
const { typesAccess } = state.type
if (lastMapView) {
const { bounds, zoom, center: _ } = lastMapView

const filteredTypes = types?.filter((typeId) => {
const type = typesAccess.getType(typeId)
return type.categories.length === 0
? categories.noCategory
: type.categories.some((cat) => categories[cat])
})
return await getClusters(
selectParams({
types,
types: filteredTypes,
muni,
invasive,
bounds,
Expand Down
13 changes: 13 additions & 0 deletions src/redux/viewChange.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,16 @@ export const selectionChanged = (types) => (dispatch) => {
dispatch(updateSelection({ types }))
dispatch(fetchLocations())
}

export const categoryChanged = (category, value) => (dispatch, getState) => {
const currentCategories = getState().filter.categories
dispatch(
updateSelection({
categories: {
...currentCategories,
[category]: value,
},
}),
)
dispatch(fetchLocations())
}
Loading