import React, { useEffect, useState } from 'react' import { CascadingMenuButton, ErrorMessage, LoadingEllipses, } from '@jbrowse/core/ui' import { getSession, useLocalStorage } from '@jbrowse/core/util' import { Button, Paper } from '@mui/material' import { observer } from 'mobx-react' import { makeStyles } from 'tss-react/mui' import { ContentCopy as CopyIcon, Difference as DifferenceIcon, Download as DownloadIcon, FormatColorFill as ColorBackgroundIcon, KeyboardArrowDown, Label as LabelIcon, PlaylistAdd as InsertionsIcon, Subject as AllLettersIcon, TableRows as TableRowsIcon, } from '@mui/icons-material' import SequenceDisplay from './SequenceDisplay' import { copyToClipboard, downloadAsFile } from '../util/clipboard' import type { MafSequenceWidgetModel } from './stateModelFactory' import type { MenuItem } from '@jbrowse/core/ui' const useStyles = makeStyles()(theme => ({ root: { padding: theme.spacing(2), }, controls: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: theme.spacing(1), marginBottom: theme.spacing(2), }, })) const MafSequenceWidget = observer(function MafSequenceWidget({ model, }: { model: MafSequenceWidgetModel }) { const { classes } = useStyles() const session = getSession(model) const { adapterConfig, samples, regions } = model const [showAllLetters, setShowAllLetters] = useLocalStorage( 'mafSequenceWidget-showAllLetters', true, ) const [includeInsertions, setIncludeInsertions] = useLocalStorage( 'mafSequenceWidget-includeInsertions', false, ) const [singleLineFormat, setSingleLineFormat] = useLocalStorage( 'mafSequenceWidget-singleLineFormat', false, ) const [colorBackground, setColorBackground] = useLocalStorage( 'mafSequenceWidget-colorBackground', true, ) const [showSampleNames, setShowSampleNames] = useLocalStorage( 'mafSequenceWidget-showSampleNames', true, ) const [rawSequences, setRawSequences] = useState([]) const [formattedSequence, setFormattedSequence] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState() useEffect(() => { if (!adapterConfig || !samples || !regions) { return } // eslint-disable-next-line @typescript-eslint/no-floating-promises ;(async () => { try { setLoading(true) setError(undefined) const { rpcManager } = session const fastaSequence = (await rpcManager.call( 'MafSequenceWidget', 'MafGetSequences', { sessionId: 'MafSequenceWidget', adapterConfig, samples, showAllLetters, includeInsertions, regions, }, )) as string[] setRawSequences(fastaSequence) let formatted: string if (singleLineFormat) { let maxLabelLength = 0 for (const s of samples) { const len = (s.label ?? s.id).length if (len > maxLabelLength) { maxLabelLength = len } } formatted = fastaSequence .map((r, idx) => { const sample = samples[idx]! const label = sample.label ?? sample.id const padding = ' '.repeat(maxLabelLength - label.length + 2) return `>${label}${padding}${r}` }) .join('\n') } else { formatted = fastaSequence .map((r, idx) => { const sample = samples[idx]! return `>${sample.label ?? sample.id}\n${r}` }) .join('\n') } setFormattedSequence(formatted) } catch (e) { console.error(e) setError(e) } finally { setLoading(false) } })() }, [ adapterConfig, samples, regions, showAllLetters, includeInsertions, singleLineFormat, session, ]) const sequenceTooLarge = formattedSequence ? formattedSequence.length > 5_000_000 : false if (!adapterConfig || !samples || !regions) { return (
No sequence data available
) } return (
{ setShowAllLetters(true) }, }, { label: 'Show only differences', icon: DifferenceIcon, type: 'radio', checked: !showAllLetters, onClick: () => { setShowAllLetters(false) }, }, { label: 'Include insertions', icon: InsertionsIcon, type: 'checkbox', checked: includeInsertions, onClick: () => { setIncludeInsertions(!includeInsertions) }, }, { label: 'Single line format', icon: TableRowsIcon, type: 'checkbox', checked: singleLineFormat, onClick: () => { setSingleLineFormat(!singleLineFormat) }, }, { label: 'Color background', icon: ColorBackgroundIcon, type: 'checkbox', checked: colorBackground, onClick: () => { setColorBackground(!colorBackground) }, }, { label: 'Show sample names', icon: LabelIcon, type: 'checkbox', checked: showSampleNames, onClick: () => { setShowSampleNames(!showSampleNames) }, }, { type: 'divider' }, { label: 'Copy to clipboard', icon: CopyIcon, disabled: loading || !formattedSequence, onClick: () => { copyToClipboard( formattedSequence, () => { session.notify('Sequence copied to clipboard', 'info') }, e => { session.notifyError(`${e}`, e) }, ).catch((e: unknown) => { console.error(e) }) }, }, { label: 'Download as FASTA', icon: DownloadIcon, disabled: loading || !formattedSequence, onClick: () => { downloadAsFile( formattedSequence, 'sequence.fasta', () => { session.notify('Sequence downloaded', 'info') }, e => { session.notifyError(`${e}`, e) }, ) }, }, ] as MenuItem[] } ButtonComponent={props => ( )} />
{error ? ( ) : ( <> {loading ? ( ) : sequenceTooLarge ? (
Reference sequence too large to display, use the Download button
) : ( )} )}
) }) export default MafSequenceWidget