Skip to content

Commit

Permalink
Add sorting and collapsing options to the hierarchical track selector (
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin authored Jul 20, 2023
1 parent de5b417 commit 612df29
Show file tree
Hide file tree
Showing 21 changed files with 1,342 additions and 1,410 deletions.
47 changes: 46 additions & 1 deletion packages/app-core/src/JBrowseConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,48 @@ export function JBrowseConfigF({
type: 'boolean',
defaultValue: false,
},

hierarchical: ConfigurationSchema('hierarchical', {
sort: ConfigurationSchema('hierarchicalSort', {
/**
* #slot configuration.hierarchical.sort.trackNames
*/
trackNames: {
type: 'boolean',
defaultValue: false,
},
/**
* #slot configuration.hierarchical.sort.categories
*/
categories: {
type: 'boolean',
defaultValue: false,
},
}),
defaultCollapsed: ConfigurationSchema('defaultCollapsed', {
/**
* #slot configuration.hierarchical.defaultCollapse.categoryNames
*/
categoryNames: {
type: 'stringArray',
defaultValue: [],
},
/**
* #slot configuration.hierarchical.defaultCollapse.topLevelCategories
*/
topLevelCategories: {
type: 'boolean',
defaultValue: false,
},
/**
* #slot configuration.hierarchical.defaultCollapse.subCategories
*/
subCategories: {
type: 'boolean',
defaultValue: false,
},
}),
}),
/**
* #slot configuration.theme
*/
Expand All @@ -107,7 +149,10 @@ export function JBrowseConfigF({
/**
* #slot configuration.extraThemes
*/
extraThemes: { type: 'frozen', defaultValue: {} },
extraThemes: {
type: 'frozen',
defaultValue: {},
},
/**
* #slot configuration.logoPath
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { createJBrowseTheme } from '@jbrowse/core/ui'
import { createTestSession } from '@jbrowse/web/src/rootModel'
import { render } from '@testing-library/react'
import { ThemeProvider } from '@mui/material/styles'
import { ThemeProvider } from '@mui/material'

// locals
import HierarchicalTrackSelector from './HierarchicalTrackSelector'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { observer } from 'mobx-react'
import AutoSizer from 'react-virtualized-auto-sizer'

// locals
import { TreeNode, HierarchicalTrackSelectorModel } from '../model'
import { HierarchicalTrackSelectorModel } from '../model'
import { TreeNode } from '../generateHierarchy'
import HierarchicalFab from './HierarchicalFab'
import HierarchicalTree from './tree/HierarchicalTree'
import HierarchicalHeader from './tree/HierarchicalHeader'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ exports[`renders nothing with no assembly 1`] = `
style="display: flex;"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-1kgqocu-MuiButtonBase-root-MuiIconButton-root-menuIcon"
aria-haspopup="true"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-9vna8i-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
Expand Down Expand Up @@ -181,7 +182,8 @@ exports[`renders with a couple of categorized tracks 1`] = `
style="display: flex;"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-1kgqocu-MuiButtonBase-root-MuiIconButton-root-menuIcon"
aria-haspopup="true"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-9vna8i-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
Expand Down Expand Up @@ -718,7 +720,8 @@ exports[`renders with a couple of uncategorized tracks 1`] = `
style="display: flex;"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-1kgqocu-MuiButtonBase-root-MuiIconButton-root-menuIcon"
aria-haspopup="true"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall css-9vna8i-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
import MoreHoriz from '@mui/icons-material/MoreHoriz'

// locals
import { matches, HierarchicalTrackSelectorModel } from '../../model'
import { HierarchicalTrackSelectorModel } from '../../model'
import { matches } from '../../util'
import FacetedHeader from './FacetedHeader'
import FacetFilters from './FacetFilters'
import { getRootKeys } from './util'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import React, { Suspense, lazy, useState } from 'react'
import { IconButton } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import JBrowseMenu from '@jbrowse/core/ui/Menu'
import {
getSession,
isSessionModelWithConnectionEditing,
Expand All @@ -14,6 +11,7 @@ import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'

// icons
import MenuIcon from '@mui/icons-material/Menu'
Expand All @@ -35,13 +33,6 @@ const ToggleConnectionsDlg = lazy(
() => import('../dialogs/ToggleConnectionsDialog'),
)

const useStyles = makeStyles()(theme => ({
menuIcon: {
marginRight: theme.spacing(1),
marginBottom: 0,
},
}))

interface ModalArgs {
connectionConf: AnyConfigurationModel
safelyBreakConnection: () => void
Expand All @@ -60,12 +51,10 @@ export default observer(function HamburgerMenu({
model: HierarchicalTrackSelectorModel
}) {
const session = getSession(model)
const [menuEl, setMenuEl] = useState<HTMLButtonElement>()
const [modalInfo, setModalInfo] = useState<ModalArgs>()
const [deleteDlgDetails, setDeleteDlgDetails] = useState<DialogDetails>()
const [connectionToggleOpen, setConnectionToggleOpen] = useState(false)
const [connectionManagerOpen, setConnectionManagerOpen] = useState(false)
const { classes } = useStyles()

function breakConnection(
connectionConf: AnyConfigurationModel,
Expand Down Expand Up @@ -93,21 +82,7 @@ export default observer(function HamburgerMenu({

return (
<>
<IconButton
className={classes.menuIcon}
onClick={event => setMenuEl(event.currentTarget)}
>
<MenuIcon />
</IconButton>

<JBrowseMenu
anchorEl={menuEl}
open={Boolean(menuEl)}
onMenuItemClick={(_, callback) => {
callback()
setMenuEl(undefined)
}}
onClose={() => setMenuEl(undefined)}
<CascadingMenuButton
menuItems={[
...(isSessionWithAddTracks(session)
? [
Expand Down Expand Up @@ -154,8 +129,40 @@ export default observer(function HamburgerMenu({
},
]
: []),
{ type: 'divider' },
{
label: 'Sort tracks by name',
type: 'checkbox',
checked: model.hSortTrackNames,
onClick: () => model.setSortTrackNames(!model.hSortTrackNames),
},
{
label: 'Sort categories by name',
type: 'checkbox',
checked: model.hSortCategories,
onClick: () => model.setSortCategories(!model.hSortCategories),
},
{ type: 'divider' },
...(model.hasAnySubcategories
? [
{
label: 'Collapse subcategories',
onClick: () => model.collapseSubCategories(),
},
]
: []),
{
label: 'Collapse top-level categories',
onClick: () => model.collapseTopLevelCategories(),
},
{
label: 'Expand all categories',
onClick: () => model.expandAllCategories(),
},
]}
/>
>
<MenuIcon />
</CascadingMenuButton>
<Suspense fallback={<React.Fragment />}>
{modalInfo ? (
<CloseConnectionDlg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ const useStyles = makeStyles()(theme => ({
},
}))

const SearchTracksTextField = observer(function ({
model,
}: {
model: HierarchicalTrackSelectorModel
}) {
const { filterText } = model
const { classes } = useStyles()
return (
<TextField
className={classes.searchBox}
label="Filter tracks"
value={filterText}
onChange={event => model.setFilterText(event.target.value)}
fullWidth
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => model.clearFilterText()}>
<ClearIcon />
</IconButton>
</InputAdornment>
),
}}
/>
)
})

function HierarchicalTrackSelectorHeader({
model,
setHeaderHeight,
Expand All @@ -33,7 +60,6 @@ function HierarchicalTrackSelectorHeader({
}) {
const { classes } = useStyles()
const [facetedOpen, setFacetedOpen] = useState(false)
const { filterText } = model

return (
<div
Expand All @@ -43,23 +69,7 @@ function HierarchicalTrackSelectorHeader({
<div style={{ display: 'flex' }}>
<HamburgerMenu model={model} />
<ShoppingCart model={model} />

<TextField
className={classes.searchBox}
label="Filter tracks"
value={filterText}
onChange={event => model.setFilterText(event.target.value)}
fullWidth
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => model.clearFilterText()}>
<ClearIcon />
</IconButton>
</InputAdornment>
),
}}
/>
<SearchTracksTextField model={model} />
<Button
className={classes.menuIcon}
onClick={() => setFacetedOpen(true)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { observer } from 'mobx-react'
import { VariableSizeTree } from 'react-vtree'
import { getSession } from '@jbrowse/core/util'
// locals
import { TreeNode, HierarchicalTrackSelectorModel } from '../../model'
import { TreeNode } from '../../generateHierarchy'
import { HierarchicalTrackSelectorModel } from '../../model'
import Node from './TrackListNode'

function getNodeData(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnyConfigurationModel } from '@jbrowse/core/configuration'
import { HierarchicalTrackSelectorModel, TreeNode } from '../model'
import { HierarchicalTrackSelectorModel } from '../model'
import { TreeNode } from '../generateHierarchy'

export interface NodeData {
nestingLevel: number
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import { getEnv, getSession, notEmpty } from '@jbrowse/core/util'
import { hasAllOverlap, hasAnyOverlap } from './util'

export function filterTracks(
tracks: AnyConfigurationModel[],
self: {
view?: {
type: string
trackSelectorAnyOverlap?: boolean
}
assemblyNames: string[]
},
) {
const { assemblyManager } = getSession(self)
const { pluginManager } = getEnv(self)
const { view } = self

if (!view) {
return []
}
const trackListAssemblies = self.assemblyNames
.map(a => assemblyManager.get(a))
.filter(notEmpty)
return tracks
.filter(c => {
const trackAssemblyNames = readConfObject(c, 'assemblyNames') as string[]
const trackAssemblies = trackAssemblyNames
?.map(name => assemblyManager.get(name))
.filter(notEmpty)
return view.trackSelectorAnyOverlap
? hasAnyOverlap(trackAssemblies, trackListAssemblies)
: hasAllOverlap(trackAssemblies, trackListAssemblies)
})
.filter(c => {
const { displayTypes } = pluginManager.getViewType(view.type)
const compatDisplays = displayTypes.map(d => d.name)
const trackDisplays = c.displays.map((d: { type: string }) => d.type)
return hasAnyOverlap(compatDisplays, trackDisplays)
})
}
Loading

0 comments on commit 612df29

Please sign in to comment.