Skip to content

Commit

Permalink
Merge pull request #1642 from GMOD/1543_locstr_from_import
Browse files Browse the repository at this point in the history
Enable locstring navigation from LGV import form
  • Loading branch information
rbuels authored Feb 4, 2021
2 parents 4e7aadd + 4a46da3 commit 52cce54
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Region } from '@jbrowse/core/util/types'
import { getSession } from '@jbrowse/core/util'
import Button from '@material-ui/core/Button'
import { makeStyles, useTheme } from '@material-ui/core/styles'
import { fade } from '@material-ui/core/styles/colorManipulator'
Expand Down Expand Up @@ -96,16 +97,29 @@ function PanControls({ model }: { model: LGV }) {
export default observer(({ model }: { model: LGV }) => {
const classes = useStyles()
const theme = useTheme()
const session = getSession(model)
const { coarseDynamicBlocks: contentBlocks, displayedRegions } = model

const setDisplayedRegion = useCallback(
(region: Region | undefined) => {
if (region) {
model.setDisplayedRegions([region])
model.showAllRegions()
(newRegionValue: string | undefined) => {
if (newRegionValue) {
const newRegion: Region | undefined = model.displayedRegions.find(
region => newRegionValue === region.refName,
)
// navigate to region or if region not found try navigating to locstring
if (newRegion) {
model.setDisplayedRegions([newRegion])
} else {
try {
newRegionValue && model.navToLocString(newRegionValue)
} catch (e) {
console.warn(e)
session.notify(`${e}`, 'warning')
}
}
}
},
[model],
[model, session],
)

const { assemblyName, refName } = contentBlocks[0] || { refName: '' }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
/* eslint-disable no-nested-ternary */
import React, { useState, useEffect } from 'react'
import { observer } from 'mobx-react'
import { getSnapshot } from 'mobx-state-tree'
import { makeStyles } from '@material-ui/core/styles'
import CircularProgress from '@material-ui/core/CircularProgress'
import { getSnapshot, Instance } from 'mobx-state-tree'
import { getSession } from '@jbrowse/core/util'
import { Region } from '@jbrowse/core/util/types/mst'
// material ui
import { makeStyles } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button'
import CircularProgress from '@material-ui/core/CircularProgress'
import TextField from '@material-ui/core/TextField'
import Container from '@material-ui/core/Container'
import Grid from '@material-ui/core/Grid'
import MenuItem from '@material-ui/core/MenuItem'
import { Region } from '@jbrowse/core/util/types'
// other
import RefNameAutocomplete from './RefNameAutocomplete'
import { LinearGenomeViewModel } from '..'

Expand All @@ -25,21 +27,24 @@ const useStyles = makeStyles(theme => ({

const ImportForm = observer(({ model }: { model: LinearGenomeViewModel }) => {
const classes = useStyles()
const session = getSession(model)
const { assemblyNames, assemblyManager } = session
const [selectedAssemblyIdx, setSelectedAssemblyIdx] = useState(0)
const [selectedRegion, setSelectedRegion] = useState<Region | undefined>()
const { assemblyNames, assemblyManager } = getSession(model)
const [selectedRegion, setSelectedRegion] = useState<string>()
const [assemblyRegions, setAssemblyRegions] = useState<
Instance<typeof Region>[]
>([])
const error = !assemblyNames.length ? 'No configured assemblies' : ''
const assemblyName = assemblyNames[selectedAssemblyIdx]
const displayName = assemblyName && !error ? selectedAssemblyIdx : ''

useEffect(() => {
let done = false
;(async () => {
if (assemblyName) {
const assembly = await assemblyManager.waitForAssembly(assemblyName)

if (!done && assembly && assembly.regions) {
setSelectedRegion(getSnapshot(assembly.regions[0]))
}
const assembly = await assemblyManager.waitForAssembly(assemblyName)
if (!done && assembly && assembly.regions) {
setSelectedRegion(assembly.regions[0].refName)
setAssemblyRegions(assembly.regions)
}
})()
return () => {
Expand All @@ -53,9 +58,25 @@ const ImportForm = observer(({ model }: { model: LinearGenomeViewModel }) => {
setSelectedAssemblyIdx(Number(event.target.value))
}

function handleSelectedRegion(input: string) {
const newRegion: Instance<typeof Region> | undefined = assemblyRegions.find(
region => selectedRegion === region.refName,
)
if (newRegion) {
model.setDisplayedRegions([getSnapshot(newRegion)])
} else {
try {
input && model.navToLocString(input, assemblyName)
} catch (e) {
console.warn(e)
session.notify(`${e}`, 'warning')
}
}
}

function onOpenClick() {
if (selectedRegion) {
model.setDisplayedRegions([selectedRegion])
handleSelectedRegion(selectedRegion)
}
}

Expand Down Expand Up @@ -90,14 +111,22 @@ const ImportForm = observer(({ model }: { model: LinearGenomeViewModel }) => {
assemblyName={
error ? undefined : assemblyNames[selectedAssemblyIdx]
}
value={selectedRegion?.refName}
value={selectedRegion}
onSelect={setSelectedRegion}
TextFieldProps={{
margin: 'normal',
variant: 'outlined',
label: 'Sequence',
className: classes.importFormEntry,
helperText: 'Select sequence to view',
helperText: 'Enter a sequence or locstring',
onBlur: event => {
setSelectedRegion((event.target as HTMLInputElement).value)
},
onKeyPress: event => {
const inputValue = (event.target as HTMLInputElement).value
if (event.key === 'Enter') {
handleSelectedRegion(inputValue)
}
},
}}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ describe('<LinearGenomeView />', () => {
session.addAssemblyConf(assemblyConf)
session.addView('LinearGenomeView', { id: 'lgv' })
const model = session.views[0]
model.setWidth(800)
const { container, findByText } = render(<LinearGenomeView model={model} />)
await findByText('Open')
const openButton = await findByText('Open')
expect(container.firstChild).toMatchSnapshot()
expect(model.displayedRegions.length).toEqual(0)
openButton.click()
expect(model.displayedRegions.length).toEqual(1)
})
it('renders one track, one region', async () => {
const session = createTestSession()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
* Based on https://material-ui.com/components/autocomplete/#Virtualize.tsx
* Asynchronous Requests for autocomplete: https://material-ui.com/components/autocomplete/
*/
import React, { useMemo } from 'react'
import { observer } from 'mobx-react'
import { Region } from '@jbrowse/core/util/types'
import { getSession } from '@jbrowse/core/util'
// material ui
import CircularProgress from '@material-ui/core/CircularProgress'
import TextField, { TextFieldProps as TFP } from '@material-ui/core/TextField'
import Typography from '@material-ui/core/Typography'
Expand All @@ -12,9 +15,7 @@ import { InputAdornment } from '@material-ui/core'
import Autocomplete, {
createFilterOptions,
} from '@material-ui/lab/Autocomplete'
import { observer } from 'mobx-react'
import { getSnapshot } from 'mobx-state-tree'
import React, { useMemo } from 'react'
// other
import { LinearGenomeViewModel } from '..'

// filter for options that were fetched
Expand All @@ -35,23 +36,23 @@ function RefNameAutocomplete({
TextFieldProps = {},
}: {
model: LinearGenomeViewModel
onSelect: (region: Region | undefined) => void
onSelect: (region: string | undefined) => void
assemblyName?: string
value?: string
style?: React.CSSProperties
TextFieldProps?: TFP
}) {
const session = getSession(model)
const { assemblyManager } = getSession(model)
const { assemblyManager } = session
const assembly = assemblyName && assemblyManager.get(assemblyName)
const regions: Region[] = (assembly && assembly.regions) || []
const { coarseVisibleLocStrings } = model
const loaded = regions.length !== 0
const options: Array<Option> = useMemo(() => {
const possOptions = regions.map(option => {
const defaultOptions = regions.map(option => {
return { type: 'reference sequence', value: option.refName }
})
return possOptions
return defaultOptions
}, [regions])

function onChange(newRegionName: Option | string) {
Expand All @@ -63,28 +64,7 @@ function RefNameAutocomplete({
if (typeof newRegionName === 'string') {
newRegionValue = newRegionName
}
const newRegion: Region | undefined = regions.find(
region => newRegionValue === region.refName,
)
if (newRegion) {
// @ts-ignore
onSelect(getSnapshot(newRegion))
} else {
newRegionValue && navTo(newRegionValue)
}
}
}

function navTo(locString: string) {
try {
if (model.displayedRegions.length !== 0) {
model.navToLocString(locString)
} else {
throw new Error(`Unknown reference sequence "${locString}"`)
}
} catch (e) {
console.warn(e)
session.notify(`${e}`, 'warning')
onSelect(newRegionValue)
}
}

Expand All @@ -97,6 +77,7 @@ function RefNameAutocomplete({
disableClearable
freeSolo
includeInputInList
clearOnBlur
loading={loaded}
selectOnFocus
style={style}
Expand All @@ -117,8 +98,7 @@ function RefNameAutocomplete({
return filtered
}}
ListboxProps={{ style: { maxHeight: 250 } }}
onChange={(e, newRegion) => {
e.preventDefault()
onChange={(_, newRegion) => {
onChange(newRegion)
}}
renderInput={params => {
Expand All @@ -144,6 +124,7 @@ function RefNameAutocomplete({
{...params}
{...TextFieldProps}
helperText={helperText}
value={coarseVisibleLocStrings || value || ''}
InputProps={TextFieldInputProps}
placeholder="Search for location"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,23 +639,70 @@ exports[`<LinearGenomeView /> renders setup wizard 1`] = `
class="MuiGrid-root MuiGrid-item"
>
<div
class="MuiCircularProgress-root MuiCircularProgress-indeterminate"
role="progressbar"
style="width: 20px; height: 20px;"
aria-expanded="false"
class="MuiAutocomplete-root"
data-testid="autocomplete"
role="combobox"
>
<svg
class="MuiCircularProgress-svg"
viewBox="22 22 44 44"
<div
class="MuiFormControl-root MuiTextField-root makeStyles-importFormEntry MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<circle
class="MuiCircularProgress-circle MuiCircularProgress-circleDisableShrink MuiCircularProgress-circleIndeterminate"
cx="44"
cy="44"
fill="none"
r="20.2"
stroke-width="3.6"
/>
</svg>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiAutocomplete-inputRoot MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-adornedEnd MuiOutlinedInput-adornedEnd"
>
<input
aria-autocomplete="list"
aria-describedby="refNameAutocomplete-lgv-helper-text"
aria-invalid="false"
autocapitalize="none"
autocomplete="off"
class="MuiInputBase-input MuiOutlinedInput-input MuiAutocomplete-input MuiAutocomplete-inputFocused MuiInputBase-inputAdornedEnd MuiOutlinedInput-inputAdornedEnd"
id="refNameAutocomplete-lgv"
placeholder="Search for location"
spellcheck="false"
type="text"
value="ctgA"
/>
<div
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
style="margin-right: 7px;"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
</div>
<div
class="MuiAutocomplete-endAdornment"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root MuiOutlinedInput-notchedOutline"
style="padding-left: 8px;"
>
<legend
class="PrivateNotchedOutline-legend"
style="width: 0.01px;"
>
<span>
</span>
</legend>
</fieldset>
</div>
<p
class="MuiFormHelperText-root MuiFormHelperText-contained MuiFormHelperText-filled"
id="refNameAutocomplete-lgv-helper-text"
>
Enter a sequence or locstring
</p>
</div>
</div>
</div>
<div
Expand Down
Loading

0 comments on commit 52cce54

Please sign in to comment.