Skip to content

Commit

Permalink
Merge pull request #1867 from GMOD/virtualized_tree
Browse files Browse the repository at this point in the history
Virtualized tree for tracklist to support having thousands of tracks
  • Loading branch information
cmdcolin authored Apr 15, 2021
2 parents 67ca1c3 + 322300e commit bcf8f30
Show file tree
Hide file tree
Showing 24 changed files with 1,398 additions and 1,643 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.1",
"@types/react-measure": "^2.0.6",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.1",
"@types/requestidlecallback": "^0.3.1",
"@types/set-value": "^2.0.0",
Expand Down
238 changes: 124 additions & 114 deletions packages/core/ui/DrawerWidget.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import Typography from '@material-ui/core/Typography'
import AppBar from '@material-ui/core/AppBar'
import IconButton from '@material-ui/core/IconButton'
import Toolbar from '@material-ui/core/Toolbar'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import React, { useState } from 'react'
import {
AppBar,
IconButton,
ListItemSecondaryAction,
MenuItem,
Select,
Toolbar,
Typography,
makeStyles,
} from '@material-ui/core'
import DeleteIcon from '@material-ui/icons/Delete'
import CloseIcon from '@material-ui/icons/Close'
import MinimizeIcon from '@material-ui/icons/Minimize'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import { makeStyles } from '@material-ui/core/styles'
import { fade } from '@material-ui/core/styles/colorManipulator'
import { observer, PropTypes } from 'mobx-react'
import { observer } from 'mobx-react'
import { getEnv } from 'mobx-state-tree'
import React from 'react'
import { useTheme } from '@material-ui/core/styles'
import Drawer from './Drawer'

const useStyles = makeStyles(theme => ({
defaultDrawer: {},
components: {
display: 'block',
},
drawerActions: {
float: 'right',
'&:hover': {
Expand Down Expand Up @@ -49,114 +48,125 @@ const useStyles = makeStyles(theme => ({
},
}))

const DrawerWidget = observer(props => {
const { session } = props
const DrawerHeader = observer(props => {
const { session, setToolbarHeight } = props
const { visibleWidget, activeWidgets } = session
const { ReactComponent } = getEnv(session).pluginManager.getWidgetType(
visibleWidget.type,
)
const classes = useStyles()

const handleChange = (e, option) => {
session.showWidget(option.props.value)
}

const theme = useTheme()
return (
<Drawer session={session} open={Boolean(activeWidgets.size)}>
<div className={classes.defaultDrawer}>
<AppBar position="sticky" color="secondary">
<Toolbar disableGutters className={classes.drawerToolbar}>
<Select
value={visibleWidget || ''}
data-testid="widget-drawer-selects"
className={classes.drawerSelect}
classes={{ icon: classes.dropDownIcon }}
renderValue={selected => {
const {
HeadingComponent: HeadingComp,
heading: headingText,
} = getEnv(session).pluginManager.getWidgetType(selected.type)
return (
<Typography variant="h6" color="inherit">
{HeadingComp ? (
<HeadingComp model={selected} />
) : (
headingText || undefined
)}
</Typography>
)
}}
onChange={(e, value) => {
handleChange(e, value)
}}
>
{Array.from(activeWidgets.values()).map((widget, index) => {
const {
HeadingComponent: HeadingComp,
heading: headingText,
} = getEnv(session).pluginManager.getWidgetType(widget.type)
return (
<MenuItem
data-testid={`widget-drawer-selects-item-${widget.type}`}
key={`${widget.id}-${index}`}
value={widget}
>
<Typography variant="h6" color="inherit">
{HeadingComp ? (
<HeadingComp model={widget} />
) : (
headingText || undefined
)}
</Typography>
<ListItemSecondaryAction>
<IconButton
className={classes.drawerCloseButton}
data-testid={`${widget.type}-drawer-delete`}
color="inherit"
aria-label="Delete"
onClick={() => {
session.hideWidget(widget)
}}
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</MenuItem>
)
})}
</Select>
<div className={classes.spacer} />
<div className={classes.drawerCloseButton}>
<IconButton
className={classes.drawerCloseButton}
data-testid="drawer-minimize"
color="inherit"
onClick={() => {
session.minimizeWidgetDrawer()
}}
<AppBar
position="sticky"
ref={ref => setToolbarHeight(ref?.getBoundingClientRect().height || 0)}
style={{ background: theme.palette.secondary.main }}
>
<Toolbar disableGutters className={classes.drawerToolbar}>
<Select
value={visibleWidget || ''}
data-testid="widget-drawer-selects"
className={classes.drawerSelect}
classes={{ icon: classes.dropDownIcon }}
renderValue={selected => {
const {
HeadingComponent: HeadingComp,
heading: headingText,
} = getEnv(session).pluginManager.getWidgetType(selected.type)
return (
<Typography variant="h6" color="inherit">
{HeadingComp ? (
<HeadingComp model={selected} />
) : (
headingText || undefined
)}
</Typography>
)
}}
onChange={(e, value) => {
handleChange(e, value)
}}
>
{Array.from(activeWidgets.values()).map((widget, index) => {
const {
HeadingComponent: HeadingComp,
heading: headingText,
} = getEnv(session).pluginManager.getWidgetType(widget.type)
return (
<MenuItem
data-testid={`widget-drawer-selects-item-${widget.type}`}
key={`${widget.id}-${index}`}
value={widget}
>
<MinimizeIcon />
</IconButton>
<IconButton
data-testid="drawer-close"
color="inherit"
onClick={() => {
session.hideWidget(visibleWidget)
}}
>
<CloseIcon />
</IconButton>
</div>
</Toolbar>
</AppBar>
<ReactComponent model={visibleWidget} session={session} />
</div>
</Drawer>
<Typography variant="h6" color="inherit">
{HeadingComp ? (
<HeadingComp model={widget} />
) : (
headingText || undefined
)}
</Typography>
<ListItemSecondaryAction>
<IconButton
className={classes.drawerCloseButton}
data-testid={`${widget.type}-drawer-delete`}
color="inherit"
aria-label="Delete"
onClick={() => {
session.hideWidget(widget)
}}
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</MenuItem>
)
})}
</Select>
<div className={classes.spacer} />
<div className={classes.drawerCloseButton}>
<IconButton
className={classes.drawerCloseButton}
data-testid="drawer-minimize"
color="inherit"
onClick={() => {
session.minimizeWidgetDrawer()
}}
>
<MinimizeIcon />
</IconButton>
<IconButton
data-testid="drawer-close"
color="inherit"
onClick={() => {
session.hideWidget(visibleWidget)
}}
>
<CloseIcon />
</IconButton>
</div>
</Toolbar>
</AppBar>
)
})

DrawerWidget.propTypes = {
session: PropTypes.observableObject.isRequired,
}
export default observer(({ session }) => {
const { visibleWidget, activeWidgets } = session
const { pluginManager } = getEnv(session)
const { ReactComponent } = pluginManager.getWidgetType(visibleWidget.type)

// we track the toolbar height because components that use virtualized height
// want to be able to fill the contained, minus the toolbar height (the
// position static/sticky is included in AutoSizer estimates)
const [toolbarHeight, setToolbarHeight] = useState(0)

export default DrawerWidget
return (
<Drawer session={session} open={Boolean(activeWidgets.size)}>
<DrawerHeader session={session} setToolbarHeight={setToolbarHeight} />
<ReactComponent
model={visibleWidget}
session={session}
toolbarHeight={toolbarHeight}
/>
</Drawer>
)
})
33 changes: 16 additions & 17 deletions packages/core/ui/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React, { useEffect, useRef, useState } from 'react'
import {
Divider,
Grow,
Expand All @@ -14,12 +15,14 @@ import {
SvgIconProps,
makeStyles,
} from '@material-ui/core'
// icons
import ArrowRightIcon from '@material-ui/icons/ArrowRight'
import CheckBoxIcon from '@material-ui/icons/CheckBox'
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked'
import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked'
import React, { useEffect, useRef, useState } from 'react'

// other
import { findLastIndex } from '../util'

const useStyles = makeStyles({
Expand Down Expand Up @@ -184,19 +187,15 @@ function findPreviousValidIdx(menuItems: MenuItem[], currentIdx: number) {
}

const MenuPage = React.forwardRef((props: MenuPageProps, ref) => {
const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<null | HTMLElement>(
null,
)
const [openSubMenuIdx, setOpenSubMenuIdx] = useState<null | number>(null)
const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<HTMLElement>()
const [openSubMenuIdx, setOpenSubMenuIdx] = useState<number>()
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
const [selectedMenuItemIdx, setSelectedMenuItemIdx] = useState<null | number>(
null,
)
const [position, setPosition] = useState<null | {
const [selectedMenuItemIdx, setSelectedMenuItemIdx] = useState<number>()
const [position, setPosition] = useState<{
top?: number
left?: number
}>(null)
const paperRef = useRef<null | HTMLDivElement>(null)
}>()
const paperRef = useRef<HTMLDivElement>()
const classes = useStyles()

const {
Expand All @@ -210,8 +209,8 @@ const MenuPage = React.forwardRef((props: MenuPageProps, ref) => {

useEffect(() => {
if (!open) {
setSubMenuAnchorEl(null)
setOpenSubMenuIdx(null)
setSubMenuAnchorEl(undefined)
setOpenSubMenuIdx(undefined)
}
}, [open])

Expand Down Expand Up @@ -329,8 +328,8 @@ const MenuPage = React.forwardRef((props: MenuPageProps, ref) => {
setOpenSubMenuIdx(idx)
}
} else {
setSubMenuAnchorEl(null)
setOpenSubMenuIdx(null)
setSubMenuAnchorEl(undefined)
setOpenSubMenuIdx(undefined)
}
}}
onKeyDown={e => {
Expand Down Expand Up @@ -372,7 +371,7 @@ const MenuPage = React.forwardRef((props: MenuPageProps, ref) => {
open={isSubMenuOpen && openSubMenuIdx === idx}
onClose={() => {
setIsSubMenuOpen(false)
setSubMenuAnchorEl(null)
setSubMenuAnchorEl(undefined)
}}
onMenuItemClick={onMenuItemClick}
menuItems={menuItem.subMenu}
Expand All @@ -389,7 +388,7 @@ const MenuPage = React.forwardRef((props: MenuPageProps, ref) => {
}

return (
<Grow in={open} style={{ transformOrigin: '0 0 0' }} ref={ref}>
<Grow in={open} style={{ transformOrigin: `0 0 0` }} ref={ref}>
<Paper
elevation={8}
ref={paperRef}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/util/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export interface AbstractSessionModel extends AbstractViewContainer {
removeAssembly?: Function
showAboutConfig?: AnyConfigurationModel
setShowAboutConfig?: Function
connections: AnyConfigurationModel[]
deleteConnection?: Function
sessionConnections?: AnyConfigurationModel[]
connectionInstances?: { name: string }[]
makeConnection?: Function
adminMode?: boolean
}
export function isSessionModel(thing: unknown): thing is AbstractSessionModel {
return (
Expand Down
5 changes: 3 additions & 2 deletions plugins/data-management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@
"dependencies": {
"@gmod/ucsc-hub": "^0.1.3",
"@material-ui/icons": "^4.9.1",
"array-intersection": "^0.1.2",
"object-hash": "^1.3.1",
"pluralize": "^8.0.0"
"pluralize": "^8.0.0",
"react-virtualized-auto-sizer": "^1.0.2",
"react-vtree": "^3.0.0-beta.1"
},
"peerDependencies": {
"@jbrowse/core": "^1.0.0",
Expand Down
Loading

0 comments on commit bcf8f30

Please sign in to comment.