Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility links to synteny feature details to allow centering a view on a feature #4624

Merged
merged 1 commit into from
Oct 31, 2024
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
36 changes: 22 additions & 14 deletions packages/app-core/src/ui/App/ViewMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ const ViewMenu = observer(function ({
label: 'View order',
type: 'subMenu' as const,
subMenu: [
{
label: 'Move view to top',
icon: KeyboardDoubleArrowUpIcon,
onClick: () => {
session.moveViewToTop(model.id)
},
},
...(session.views.length > 2
? [
{
label: 'Move view to top',
icon: KeyboardDoubleArrowUpIcon,
onClick: () => {
session.moveViewToTop(model.id)
},
},
]
: []),
{
label: 'Move view up',
icon: KeyboardArrowUpIcon,
Expand All @@ -90,13 +94,17 @@ const ViewMenu = observer(function ({
session.moveViewDown(model.id)
},
},
{
label: 'Move view to bottom',
icon: KeyboardDoubleArrowDownIcon,
onClick: () => {
session.moveViewToBottom(model.id)
},
},
...(session.views.length > 2
? [
{
label: 'Move view to bottom',
icon: KeyboardDoubleArrowDownIcon,
onClick: () => {
session.moveViewToBottom(model.id)
},
},
]
: []),
],
},
]
Expand Down
41 changes: 41 additions & 0 deletions packages/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Typography,
} from '@mui/material'
import { makeStyles } from 'tss-react/mui'

// icons
import ExpandMore from '@mui/icons-material/ExpandMore'

import { BaseCardProps } from '../types'

const useStyles = makeStyles()(theme => ({
expansionPanelDetails: {
display: 'block',
padding: theme.spacing(1),
},
icon: {
color: theme.palette.tertiary.contrastText || '#fff',
},
}))

export default function BaseCard({
children,
title,
defaultExpanded = true,
}: BaseCardProps) {
const { classes } = useStyles()
return (
<Accordion defaultExpanded={defaultExpanded}>
<AccordionSummary expandIcon={<ExpandMore className={classes.icon} />}>
<Typography variant="button">{title}</Typography>
</AccordionSummary>
<AccordionDetails className={classes.expansionPanelDetails}>
{children}
</AccordionDetails>
</Accordion>
)
}
53 changes: 53 additions & 0 deletions packages/core/BaseFeatureWidget/BaseFeatureDetail/CoreDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
// locals
import { toLocale, SimpleFeatureSerialized } from '../../util'
import { BaseProps } from '../types'
import SimpleField from './SimpleField'
import Position from './Position'

export default function CoreDetails(props: BaseProps) {
const { feature } = props
const obj = feature as SimpleFeatureSerialized & {
start: number
end: number
assemblyName?: string
strand: number
refName: string
__jbrowsefmt: {
start?: number
assemblyName?: string
end?: number
refName?: string
name?: string
}
}

const formattedFeat = { ...obj, ...obj.__jbrowsefmt }
const { start, end } = formattedFeat

const displayedDetails: Record<string, any> = {
...formattedFeat,
length: toLocale(end - start),
}

const coreRenderedDetails = {
description: 'Description',
name: 'Name',
length: 'Length',
type: 'Type',
}
return (
<>
<SimpleField
name="Position"
value={<Position {...props} feature={formattedFeat} />}
/>
{Object.entries(coreRenderedDetails)
.map(([key, name]) => [name, displayedDetails[key]])
.filter(([, value]) => value != null)
.map(([name, value]) => (
<SimpleField key={name} name={name} value={value} />
))}
</>
)
}
111 changes: 111 additions & 0 deletions packages/core/BaseFeatureWidget/BaseFeatureDetail/FeatureDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react'
import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary'
import { Divider, Typography } from '@mui/material'
import { IAnyStateTreeNode } from 'mobx-state-tree'

// locals
import { getEnv, getSession, SimpleFeatureSerialized } from '../../util'
import { ErrorMessage } from '../../ui'
import { generateTitle } from './util'
import SequenceFeatureDetails from '../SequenceFeatureDetails'
import Attributes from './Attributes'
import BaseCard from './BaseCard'
import CoreDetails from './CoreDetails'

// coreDetails are omitted in some circumstances
const coreDetails = [
'name',
'start',
'end',
'strand',
'refName',
'description',
'type',
]

interface PanelDescriptor {
name: string
Component: React.FC<any>
}

export default function FeatureDetails(props: {
model: IAnyStateTreeNode
feature: SimpleFeatureSerialized
depth?: number
omit?: string[]
descriptions?: Record<string, React.ReactNode>
formatter?: (val: unknown, key: string) => React.ReactNode
}) {
const { omit = [], model, feature, depth = 0 } = props
const { maxDepth } = model
const { mate, name = '', id = '', type = '', subfeatures, uniqueId } = feature
const pm = getEnv(model).pluginManager
const session = getSession(model)

const ExtraPanel = pm.evaluateExtensionPoint('Core-extraFeaturePanel', null, {
session,
feature,
model,
}) as PanelDescriptor | undefined
const m = mate as { start: number; end: number; refName: string } | undefined
return (
<BaseCard title={generateTitle(name, id, type)}>
<Typography>Core details</Typography>
<CoreDetails {...props} />
{m ? (
<>
<Divider />
<Typography>Mate details</Typography>
<CoreDetails
{...props}
feature={{
...m,
start: m.start,
end: m.end,
refName: m.refName,
uniqueId: `${uniqueId}-mate`,
}}
/>
</>
) : null}

<Divider />
<Typography>Attributes</Typography>
<Attributes
attributes={feature}
{...props}
omit={omit}
omitSingleLevel={coreDetails}
/>

<ErrorBoundary FallbackComponent={e => <ErrorMessage error={e.error} />}>
<SequenceFeatureDetails {...props} />
</ErrorBoundary>

{ExtraPanel ? (
<>
<Divider />
<BaseCard title={ExtraPanel.name}>
<ExtraPanel.Component {...props} />
</BaseCard>
</>
) : null}

{depth < maxDepth && subfeatures?.length ? (
<BaseCard title="Subfeatures" defaultExpanded={depth < 1}>
{subfeatures.map((sub, idx) => (
<FeatureDetails
key={JSON.stringify(sub)}
feature={{
...sub,
uniqueId: `${uniqueId}_${idx}`,
}}
model={model}
depth={depth + 1}
/>
))}
</BaseCard>
) : null}
</BaseCard>
)
}
16 changes: 16 additions & 0 deletions packages/core/BaseFeatureWidget/BaseFeatureDetail/Position.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { assembleLocString } from '../../util'
import { BaseProps } from '../types'

export default function Position(props: BaseProps) {
const { feature } = props
const strand = feature.strand as number
const strandMap: Record<string, string> = {
'-1': '-',
'0': '',
'1': '+',
}
const str = strandMap[strand] ? `(${strandMap[strand]})` : ''
const loc = assembleLocString(feature)
return <>{`${loc} ${str}`}</>
}
Loading
Loading