Skip to content

Commit

Permalink
Refactor faceted track selector to use more MST state (#4094)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Nov 29, 2023
1 parent 2502c2c commit 2571245
Show file tree
Hide file tree
Showing 13 changed files with 1,000 additions and 1,039 deletions.
25 changes: 13 additions & 12 deletions packages/core/ui/CascadingMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useMemo, useCallback } from 'react'
import React, { useContext, useMemo } from 'react'
import {
Divider,
ListItemIcon,
Expand Down Expand Up @@ -37,15 +37,16 @@ function CascadingMenuItem({
if (!rootPopupState) {
throw new Error('must be used inside a CascadingMenu')
}
const handleClick = useCallback(
(event: React.MouseEvent) => {
rootPopupState.close()
onClick?.(event)
},
[rootPopupState, onClick],
)

return <MenuItem {...props} onClick={handleClick} />
return (
<MenuItem
{...props}
onClick={event => {
rootPopupState.close()
onClick?.(event)
}}
/>
)
}

function CascadingSubmenu({
Expand All @@ -64,7 +65,7 @@ function CascadingSubmenu({
menuItems: JBMenuItem[]
popupId: string
}) {
const { parentPopupState } = React.useContext(CascadingContext)
const { parentPopupState } = useContext(CascadingContext)
const popupState = usePopupState({
popupId,
variant: 'popover',
Expand Down Expand Up @@ -131,8 +132,8 @@ function CascadingMenu({
onMenuItemClick: Function
menuItems: JBMenuItem[]
}) {
const { rootPopupState } = React.useContext(CascadingContext)
const context = React.useMemo(
const { rootPopupState } = useContext(CascadingContext)
const context = useMemo(
() => ({
rootPopupState: rootPopupState || popupState,
parentPopupState: popupState,
Expand Down
9 changes: 8 additions & 1 deletion packages/core/ui/SanitizedHTML.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ function isHTML(str: string) {
// products/jbrowse-web/src/tests/Connection.test.tsx test (can delete mock to
// see)
//
export default function SanitizedHTML({ html: pre }: { html: string }) {
export default function SanitizedHTML({
html: pre,
className,
}: {
className?: string
html: string
}) {
// try to add links to the text first
const html = linkify(pre)
const value = isHTML(html) ? html : escapeHTML(html)
Expand All @@ -82,6 +88,7 @@ export default function SanitizedHTML({ html: pre }: { html: string }) {

return (
<span
className={className}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: dompurify.sanitize(value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import ClearIcon from '@mui/icons-material/Clear'
import MinimizeIcon from '@mui/icons-material/Minimize'
import AddIcon from '@mui/icons-material/Add'
import { coarseStripHTML } from '@jbrowse/core/util'
import { observer } from 'mobx-react'
import { HierarchicalTrackSelectorModel } from '../../model'

const useStyles = makeStyles()(theme => ({
facet: {
Expand Down Expand Up @@ -50,42 +52,44 @@ function ExpandButton({
)
}

export default function FacetFilter({
const FacetFilter = observer(function ({
column,
vals,
width,
dispatch,
filters,
model,
}: {
column: { field: string }
vals: [string, number][]
width: number
dispatch: (arg: { key: string; val: string[] }) => void
filters: Record<string, string[]>
model: HierarchicalTrackSelectorModel
}) {
const { classes } = useStyles()
const [visible, setVisible] = useState(true)
const { faceted } = model
const { filters } = faceted
return (
<FormControl key={column.field} className={classes.facet} style={{ width }}>
<div style={{ display: 'flex' }}>
<Typography>{column.field}</Typography>
<ClearButton onClick={() => dispatch({ key: column.field, val: [] })} />
<ClearButton
onClick={() => model.faceted.setFilter(column.field, [])}
/>
<ExpandButton visible={visible} onClick={() => setVisible(!visible)} />
</div>
{visible ? (
<Select
multiple
native
className={classes.select}
value={filters[column.field]}
value={filters.get(column.field) || []}
onChange={event => {
dispatch({
key: column.field,
faceted.setFilter(
column.field,
// @ts-expect-error
val: [...event.target.options]
[...event.target.options]
.filter(opt => opt.selected)
.map(opt => opt.value),
})
)
}}
>
{vals
Expand All @@ -99,4 +103,6 @@ export default function FacetFilter({
) : null}
</FormControl>
)
}
})

export default FacetFilter
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React from 'react'
import FacetFilter from './FacetFilter'
import { HierarchicalTrackSelectorModel } from '../../model'
import { observer } from 'mobx-react'

export default function FacetFilters({
const FacetFilters = observer(function ({
rows,
columns,
dispatch,
filters,
width,
model,
}: {
rows: Record<string, unknown>[]
filters: Record<string, string[]>
columns: { field: string }[]
dispatch: (arg: { key: string; val: string[] }) => void
width: number
model: HierarchicalTrackSelectorModel
}) {
const { faceted } = model
const { filters } = faceted
const facets = columns.slice(1)
const uniqs = new Map(
facets.map(f => [f.field, new Map<string, number>()] as const),
Expand All @@ -22,12 +24,12 @@ export default function FacetFilters({
// this code "stages the facet filters" in order that the user has selected
// them, which relies on the js behavior that the order of the returned keys is
// related to the insertion order.
const filterKeys = Object.keys(filters)
const filterKeys = faceted.filters.keys()
const facetKeys = facets.map(f => f.field)
const ret = new Set<string>()
for (const entry of filterKeys) {
// give non-empty filters priority
if (filters[entry]?.length) {
if (filters.get(entry)?.length) {
ret.add(entry)
}
}
Expand All @@ -50,26 +52,26 @@ export default function FacetFilters({
}
}
}
const filter = filters[facet]?.length ? new Set(filters[facet]) : undefined
const filter = filters.get(facet)?.length
? new Set(filters.get(facet))
: undefined
currentRows = currentRows.filter(row => {
return filter !== undefined ? filter.has(row[facet] as string) : true
})
}

return (
<div>
{facets.map(column => {
return (
<FacetFilter
key={column.field}
vals={[...uniqs.get(column.field)!]}
column={column}
width={width}
dispatch={dispatch}
filters={filters}
/>
)
})}
{facets.map(column => (
<FacetFilter
key={column.field}
vals={[...uniqs.get(column.field)!]}
column={column}
width={width}
model={model}
/>
))}
</div>
)
}
})
export default FacetFilters
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,26 @@ import ShoppingCart from '../ShoppingCart'
import { HierarchicalTrackSelectorModel } from '../../model'

export default function FacetedHeader({
setFilterText,
setUseShoppingCart,
setShowSparse,
setShowFilters,
setShowOptions,
showOptions,
showSparse,
showFilters,
useShoppingCart,
filterText,
model,
}: {
setFilterText: (arg: string) => void
setUseShoppingCart: (arg: boolean) => void
setShowSparse: (arg: boolean) => void
setShowFilters: (arg: boolean) => void
setShowOptions: (arg: boolean) => void
filterText: string
showOptions: boolean
useShoppingCart: boolean
showSparse: boolean
showFilters: boolean
model: HierarchicalTrackSelectorModel
}) {
const { faceted } = model
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
const { showOptions, showFilters, showSparse, useShoppingCart } = faceted

return (
<>
<Grid container spacing={4} alignItems="center">
<Grid item>
<TextField
label="Search..."
value={filterText}
onChange={event => setFilterText(event.target.value)}
value={faceted.filterText}
onChange={event => faceted.setFilterText(event.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setFilterText('')}>
<IconButton onClick={() => faceted.setFilterText('')}>
<ClearIcon />
</IconButton>
</InputAdornment>
Expand Down Expand Up @@ -77,25 +59,25 @@ export default function FacetedHeader({
menuItems={[
{
label: 'Add tracks to selection instead of turning them on/off',
onClick: () => setUseShoppingCart(!useShoppingCart),
onClick: () => faceted.setUseShoppingCart(!useShoppingCart),
type: 'checkbox',
checked: useShoppingCart,
},
{
label: 'Show sparse metadata columns',
onClick: () => setShowSparse(!showSparse),
onClick: () => faceted.setShowSparse(!showSparse),
checked: showSparse,
type: 'checkbox',
},
{
label: 'Show facet filters',
onClick: () => setShowFilters(!showFilters),
onClick: () => faceted.setShowFilters(!showFilters),
checked: showFilters,
type: 'checkbox',
},
{
label: 'Show extra table options',
onClick: () => setShowOptions(!showOptions),
onClick: () => faceted.setShowOptions(!showOptions),
checked: showOptions,
type: 'checkbox',
},
Expand Down
Loading

0 comments on commit 2571245

Please sign in to comment.