Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
path: dist/

test:
name: Test (${{ matrix.jbrowse-version }})
name: Test production builds
needs: build-and-lint
runs-on: ubuntu-latest
strategy:
Expand Down
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,6 @@
}
]
}
]
],
"aggregateTextSearchAdapters": []
}
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig(
'esbuild-watch.mjs',
'eslint.config.mjs',
'ucsc/*',
'.test*',
'.test-jbrowse/*',
],
},
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"build": "tsc && NODE_ENV=production node esbuild.mjs && cp distconfig.json dist/config.json",
"prebuild": "yarn clean",
"lint": "eslint --report-unused-disable-directives --max-warnings 0",
"pretest": "test -d .test-jbrowse || npx @jbrowse/cli create .test-jbrowse --nightly",
"pretest": "rm -rf .test-jbrowse && npx @jbrowse/cli create .test-jbrowse --nightly",
"test": "vitest run",
"test:watch": "vitest",
"test:setup": "node scripts/test-versions.mjs setup",
Expand All @@ -31,8 +31,9 @@
"dependencies": {
"@emotion/styled": "^11.14.1",
"g2p_mapper": "^2.0.0",
"pako": "^2.1.0",
"react-msaview": "^5.0.3",
"idb": "^8.0.3",
"pako-esm2": "^2.0.0",
"react-msaview": "^5.0.5",
"swr": "^2.3.8"
},
"devDependencies": {
Expand All @@ -58,7 +59,6 @@
"eslint-plugin-unicorn": "^62.0.0",
"mobx": "^6.15.0",
"mobx-react": "^9.2.1",
"msa-parsers": "^5.0.3",
"prettier": "^3.7.4",
"pretty-bytes": "^7.1.0",
"puppeteer": "^24.34.0",
Expand Down
58 changes: 37 additions & 21 deletions src/AddHighlightModel/GenomeMouseoverHighlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,51 @@ const GenomeMouseoverHighlight = observer(function ({
}: {
model: LinearGenomeViewModel
}) {
const { hovered } = getSession(model)
return hovered &&
typeof hovered === 'object' &&
'hoverPosition' in hovered ? (
<GenomeMouseoverHighlightPostNullCheck model={model} />
) : null
const session = getSession(model)
const { hovered, views } = session

// Early return if no MSA view exists
const hasMsaView = views.some(s => s.type === 'MsaView')
if (!hasMsaView) {
return null
}

// Early return if no hover position
if (
!hovered ||
typeof hovered !== 'object' ||
!('hoverPosition' in hovered)
) {
return null
}

return <GenomeMouseoverHighlightRenderer model={model} hovered={hovered} />
})

const GenomeMouseoverHighlightPostNullCheck = observer(function ({
const GenomeMouseoverHighlightRenderer = observer(function ({
model,
hovered,
}: {
model: LinearGenomeViewModel

hovered: any
}) {
const { classes } = useStyles()
const session = getSession(model)
if (session.views.some(s => s.type === 'MsaView')) {
const { hovered } = session
const { offsetPx } = model
// @ts-expect-error
const { coord, refName } = hovered.hoverPosition

const s = model.bpToPx({ refName, coord: coord - 1 })
const e = model.bpToPx({ refName, coord: coord })
if (s && e) {
const width = Math.max(Math.abs(e.offsetPx - s.offsetPx), 4)
const left = Math.min(s.offsetPx, e.offsetPx) - offsetPx
return <div className={classes.highlight} style={{ left, width }} />
}
const { offsetPx } = model
const { coord, refName } = hovered.hoverPosition as {
coord: number
refName: string
}

const s = model.bpToPx({ refName, coord: coord - 1 })
const e = model.bpToPx({ refName, coord: coord })

if (s && e) {
const width = Math.max(Math.abs(e.offsetPx - s.offsetPx), 4)
const left = Math.min(s.offsetPx, e.offsetPx) - offsetPx
return <div className={classes.highlight} style={{ left, width }} />
}

return null
})

Expand Down
55 changes: 38 additions & 17 deletions src/AddHighlightModel/MsaToGenomeHighlight.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,74 @@
import React from 'react'

import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
import { getSession } from '@jbrowse/core/util'
import { observer } from 'mobx-react'

import { useStyles } from './util'
import { JBrowsePluginMsaViewModel } from '../MsaViewPanel/model'

import type { JBrowsePluginMsaViewModel } from '../MsaViewPanel/model'
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'

type LGV = LinearGenomeViewModel

function getCanonicalName(assembly: Assembly, s: string) {
return assembly.getCanonicalRefName(s) ?? s
}

// Outer component: only re-renders when MSA view or highlights change
const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({
model,
}: {
model: LGV
}) {
const { classes } = useStyles()
const { assemblyManager, views } = getSession(model)
const p = views.find(f => f.type === 'MsaView') as
const { views } = getSession(model)
const msaView = views.find(f => f.type === 'MsaView') as
| JBrowsePluginMsaViewModel
| undefined

const highlights = msaView?.connectedHighlights

// Early return if no highlights - avoid all other work
if (!highlights || highlights.length === 0) {
return null
}

return <MsaToGenomeHighlightRenderer model={model} highlights={highlights} />
})

// Inner component: handles the scroll-dependent rendering
const MsaToGenomeHighlightRenderer = observer(function ({
model,
highlights,
}: {
model: LGV
highlights: { refName: string; start: number; end: number }[]
}) {
const { classes } = useStyles()
const { assemblyManager } = getSession(model)
const assembly = assemblyManager.get(model.assemblyNames[0]!)
return assembly ? (
const { offsetPx } = model

if (!assembly) {
return null
}

return (
<>
{p?.connectedHighlights.map((r, idx) => {
const refName = getCanonicalName(assembly, r.refName)
{highlights.map((r, idx) => {
const refName = assembly.getCanonicalRefName(r.refName) ?? r.refName
const s = model.bpToPx({ refName, coord: r.start })
const e = model.bpToPx({ refName, coord: r.end })
if (s && e) {
const width = Math.max(Math.abs(e.offsetPx - s.offsetPx), 4)
const left = Math.min(s.offsetPx, e.offsetPx) - model.offsetPx
const left = Math.min(s.offsetPx, e.offsetPx) - offsetPx
return (
<div
key={`${JSON.stringify(r)}-${idx}`}
key={`${r.refName}-${r.start}-${r.end}-${idx}`}
className={classes.highlight}
style={{ left, width }}
/>
)
} else {
return null
}
return null
})}
</>
) : null
)
})

export default MsaToGenomeHighlight
8 changes: 8 additions & 0 deletions src/AddHighlightModel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'

import PluginManager from '@jbrowse/core/PluginManager'
import { getSession } from '@jbrowse/core/util'

import HighlightComponents from './HighlightComponents'

Expand All @@ -13,6 +14,13 @@ export default function AddHighlightComponentsModelF(
'LinearGenomeView-TracksContainerComponent',
// @ts-expect-error
(rest: React.ReactNode[], { model }: { model: LinearGenomeViewModel }) => {
// Quick check: don't add any components if no MSA view exists
const { views } = getSession(model)
const hasMsaView = views.some(v => v.type === 'MsaView')
if (!hasMsaView) {
return rest
}

return [
...rest,
<HighlightComponents
Expand Down
53 changes: 30 additions & 23 deletions src/LaunchMsaView/components/LaunchMsaViewDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState } from 'react'

import { readConfObject } from '@jbrowse/core/configuration'
import { Dialog } from '@jbrowse/core/ui'
import { AbstractTrackModel, Feature } from '@jbrowse/core/util'
import { AbstractTrackModel, Feature, getSession } from '@jbrowse/core/util'
import { Tab, Tabs } from '@mui/material'

import EnsemblGeneTree from './EnsemblGeneTree/EnsemblGeneTree'
Expand All @@ -10,12 +11,7 @@ import NCBIBlastPanel from './NCBIBlastQuery/NCBIBlastPanel'
import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel'
import TabPanel from './TabPanel'

const TABS = {
NCBI_BLAST: 0,
PRELOADED_MSA: 1,
ENSEMBL_GENETREE: 2,
MANUAL_MSA: 3,
}
import type { Dataset } from './PreLoadedMSA/types'

export default function LaunchMsaViewDialog({
handleClose,
Expand All @@ -26,42 +22,53 @@ export default function LaunchMsaViewDialog({
feature: Feature
model: AbstractTrackModel
}) {
const [value, setValue] = useState(TABS.NCBI_BLAST)
const session = getSession(model)
const { jbrowse } = session
const datasets = readConfObject(jbrowse, ['msa', 'datasets']) as
| Dataset[]
| undefined
const hasPreloadedDatasets = datasets && datasets.length > 0

const [value, setValue] = useState('ncbi_blast')

const handleChange = (_event: React.SyntheticEvent, newValue: number) => {
const handleChange = (_event: React.SyntheticEvent, newValue: string) => {
setValue(newValue)
}

return (
<Dialog maxWidth="xl" title="Launch MSA view" open onClose={handleClose}>
<Tabs value={value} onChange={handleChange}>
<Tab label="NCBI BLAST query" value={TABS.NCBI_BLAST} />
<Tab label="Pre-loaded MSA datasets" value={TABS.PRELOADED_MSA} />
<Tab label="Ensembl GeneTree" value={TABS.ENSEMBL_GENETREE} />
<Tab label="Manual upload" value={TABS.MANUAL_MSA} />
<Tab label="NCBI BLAST query" value="ncbi_blast" />
{hasPreloadedDatasets ? (
<Tab label="Pre-loaded MSA datasets" value="preloaded_msa" />
) : null}
<Tab label="Ensembl GeneTree" value="ensembl_genetree" />
<Tab label="Manual upload" value="manual_msa" />
</Tabs>
<TabPanel value={value} index={TABS.NCBI_BLAST}>
<TabPanel value={value} index="ncbi_blast">
<NCBIBlastPanel
handleClose={handleClose}
feature={feature}
model={model}
/>
</TabPanel>
<TabPanel value={value} index={TABS.PRELOADED_MSA}>
<PreLoadedMSA
model={model}
feature={feature}
handleClose={handleClose}
/>
</TabPanel>
<TabPanel value={value} index={TABS.ENSEMBL_GENETREE}>
{hasPreloadedDatasets ? (
<TabPanel value={value} index="preloaded_msa">
<PreLoadedMSA
model={model}
feature={feature}
handleClose={handleClose}
/>
</TabPanel>
) : null}
<TabPanel value={value} index="ensembl_genetree">
<EnsemblGeneTree
model={model}
feature={feature}
handleClose={handleClose}
/>
</TabPanel>
<TabPanel value={value} index={TABS.MANUAL_MSA}>
<TabPanel value={value} index="manual_msa">
<ManualMSALoader
model={model}
feature={feature}
Expand Down
Loading